introduce Username, Realm and Userid api types
and begin splitting up types.rs as it has grown quite large already Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
@ -15,6 +15,8 @@ use proxmox::tools::{fs::replace_file, fs::CreateOptions};
|
||||
use proxmox::constnamemap;
|
||||
use proxmox::api::{api, schema::*};
|
||||
|
||||
use crate::api2::types::Userid;
|
||||
|
||||
// define Privilege bitfield
|
||||
|
||||
constnamemap! {
|
||||
@ -224,7 +226,7 @@ pub struct AclTree {
|
||||
}
|
||||
|
||||
pub struct AclTreeNode {
|
||||
pub users: HashMap<String, HashMap<String, bool>>,
|
||||
pub users: HashMap<Userid, HashMap<String, bool>>,
|
||||
pub groups: HashMap<String, HashMap<String, bool>>,
|
||||
pub children: BTreeMap<String, AclTreeNode>,
|
||||
}
|
||||
@ -239,7 +241,7 @@ impl AclTreeNode {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_roles(&self, user: &str, all: bool) -> HashSet<String> {
|
||||
pub fn extract_roles(&self, user: &Userid, all: bool) -> HashSet<String> {
|
||||
let user_roles = self.extract_user_roles(user, all);
|
||||
if !user_roles.is_empty() {
|
||||
// user privs always override group privs
|
||||
@ -249,7 +251,7 @@ impl AclTreeNode {
|
||||
self.extract_group_roles(user, all)
|
||||
}
|
||||
|
||||
pub fn extract_user_roles(&self, user: &str, all: bool) -> HashSet<String> {
|
||||
pub fn extract_user_roles(&self, user: &Userid, all: bool) -> HashSet<String> {
|
||||
|
||||
let mut set = HashSet::new();
|
||||
|
||||
@ -273,7 +275,7 @@ impl AclTreeNode {
|
||||
set
|
||||
}
|
||||
|
||||
pub fn extract_group_roles(&self, _user: &str, all: bool) -> HashSet<String> {
|
||||
pub fn extract_group_roles(&self, _user: &Userid, all: bool) -> HashSet<String> {
|
||||
|
||||
let mut set = HashSet::new();
|
||||
|
||||
@ -305,7 +307,7 @@ impl AclTreeNode {
|
||||
roles.remove(role);
|
||||
}
|
||||
|
||||
pub fn delete_user_role(&mut self, userid: &str, role: &str) {
|
||||
pub fn delete_user_role(&mut self, userid: &Userid, role: &str) {
|
||||
let roles = match self.users.get_mut(userid) {
|
||||
Some(r) => r,
|
||||
None => return,
|
||||
@ -324,7 +326,7 @@ impl AclTreeNode {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_user_role(&mut self, user: String, role: String, propagate: bool) {
|
||||
pub fn insert_user_role(&mut self, user: Userid, role: String, propagate: bool) {
|
||||
let map = self.users.entry(user).or_insert_with(|| HashMap::new());
|
||||
if role == ROLE_NAME_NO_ACCESS {
|
||||
map.clear();
|
||||
@ -376,7 +378,7 @@ impl AclTree {
|
||||
node.delete_group_role(group, role);
|
||||
}
|
||||
|
||||
pub fn delete_user_role(&mut self, path: &str, userid: &str, role: &str) {
|
||||
pub fn delete_user_role(&mut self, path: &str, userid: &Userid, role: &str) {
|
||||
let path = split_acl_path(path);
|
||||
let node = match self.get_node(&path) {
|
||||
Some(n) => n,
|
||||
@ -391,10 +393,10 @@ impl AclTree {
|
||||
node.insert_group_role(group.to_string(), role.to_string(), propagate);
|
||||
}
|
||||
|
||||
pub fn insert_user_role(&mut self, path: &str, user: &str, role: &str, propagate: bool) {
|
||||
pub fn insert_user_role(&mut self, path: &str, user: &Userid, role: &str, propagate: bool) {
|
||||
let path = split_acl_path(path);
|
||||
let node = self.get_or_insert_node(&path);
|
||||
node.insert_user_role(user.to_string(), role.to_string(), propagate);
|
||||
node.insert_user_role(user.to_owned(), role.to_string(), propagate);
|
||||
}
|
||||
|
||||
fn write_node_config(
|
||||
@ -521,7 +523,7 @@ impl AclTree {
|
||||
let group = &user_or_group[1..];
|
||||
node.insert_group_role(group.to_string(), role.to_string(), propagate);
|
||||
} else {
|
||||
node.insert_user_role(user_or_group.to_string(), role.to_string(), propagate);
|
||||
node.insert_user_role(user_or_group.parse()?, role.to_string(), propagate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -569,7 +571,7 @@ impl AclTree {
|
||||
Ok(tree)
|
||||
}
|
||||
|
||||
pub fn roles(&self, userid: &str, path: &[&str]) -> HashSet<String> {
|
||||
pub fn roles(&self, userid: &Userid, path: &[&str]) -> HashSet<String> {
|
||||
|
||||
let mut node = &self.root;
|
||||
let mut role_set = node.extract_roles(userid, path.is_empty());
|
||||
@ -665,13 +667,14 @@ pub fn save_config(acl: &AclTree) -> Result<(), Error> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use anyhow::{Error};
|
||||
use super::AclTree;
|
||||
|
||||
use crate::api2::types::Userid;
|
||||
|
||||
fn check_roles(
|
||||
tree: &AclTree,
|
||||
user: &str,
|
||||
user: &Userid,
|
||||
path: &str,
|
||||
expected_roles: &str,
|
||||
) {
|
||||
@ -686,22 +689,23 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_acl_line_compression() -> Result<(), Error> {
|
||||
fn test_acl_line_compression() {
|
||||
|
||||
let tree = AclTree::from_raw(r###"
|
||||
acl:0:/store/store2:user1:Admin
|
||||
acl:0:/store/store2:user2:Admin
|
||||
acl:0:/store/store2:user1:DatastoreBackup
|
||||
acl:0:/store/store2:user2:DatastoreBackup
|
||||
"###)?;
|
||||
let tree = AclTree::from_raw(
|
||||
"\
|
||||
acl:0:/store/store2:user1@pbs:Admin\n\
|
||||
acl:0:/store/store2:user2@pbs:Admin\n\
|
||||
acl:0:/store/store2:user1@pbs:DatastoreBackup\n\
|
||||
acl:0:/store/store2:user2@pbs:DatastoreBackup\n\
|
||||
",
|
||||
)
|
||||
.expect("failed to parse acl tree");
|
||||
|
||||
let mut raw: Vec<u8> = Vec::new();
|
||||
tree.write_config(&mut raw)?;
|
||||
let raw = std::str::from_utf8(&raw)?;
|
||||
tree.write_config(&mut raw).expect("failed to write acl tree");
|
||||
let raw = std::str::from_utf8(&raw).expect("acl tree is not valid utf8");
|
||||
|
||||
assert_eq!(raw, "acl:0:/store/store2:user1,user2:Admin,DatastoreBackup\n");
|
||||
|
||||
Ok(())
|
||||
assert_eq!(raw, "acl:0:/store/store2:user1@pbs,user2@pbs:Admin,DatastoreBackup\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -712,15 +716,17 @@ acl:1:/storage:user1@pbs:Admin
|
||||
acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
||||
acl:1:/storage/store2:user2@pbs:DatastoreBackup
|
||||
"###)?;
|
||||
check_roles(&tree, "user1@pbs", "/", "");
|
||||
check_roles(&tree, "user1@pbs", "/storage", "Admin");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store1", "DatastoreBackup");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store2", "Admin");
|
||||
let user1: Userid = "user1@pbs".parse()?;
|
||||
check_roles(&tree, &user1, "/", "");
|
||||
check_roles(&tree, &user1, "/storage", "Admin");
|
||||
check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
|
||||
check_roles(&tree, &user1, "/storage/store2", "Admin");
|
||||
|
||||
check_roles(&tree, "user2@pbs", "/", "");
|
||||
check_roles(&tree, "user2@pbs", "/storage", "");
|
||||
check_roles(&tree, "user2@pbs", "/storage/store1", "");
|
||||
check_roles(&tree, "user2@pbs", "/storage/store2", "DatastoreBackup");
|
||||
let user2: Userid = "user2@pbs".parse()?;
|
||||
check_roles(&tree, &user2, "/", "");
|
||||
check_roles(&tree, &user2, "/storage", "");
|
||||
check_roles(&tree, &user2, "/storage/store1", "");
|
||||
check_roles(&tree, &user2, "/storage/store2", "DatastoreBackup");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -733,22 +739,23 @@ acl:1:/:user1@pbs:Admin
|
||||
acl:1:/storage:user1@pbs:NoAccess
|
||||
acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
||||
"###)?;
|
||||
check_roles(&tree, "user1@pbs", "/", "Admin");
|
||||
check_roles(&tree, "user1@pbs", "/storage", "NoAccess");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store1", "DatastoreBackup");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store2", "NoAccess");
|
||||
check_roles(&tree, "user1@pbs", "/system", "Admin");
|
||||
let user1: Userid = "user1@pbs".parse()?;
|
||||
check_roles(&tree, &user1, "/", "Admin");
|
||||
check_roles(&tree, &user1, "/storage", "NoAccess");
|
||||
check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
|
||||
check_roles(&tree, &user1, "/storage/store2", "NoAccess");
|
||||
check_roles(&tree, &user1, "/system", "Admin");
|
||||
|
||||
let tree = AclTree::from_raw(r###"
|
||||
acl:1:/:user1@pbs:Admin
|
||||
acl:0:/storage:user1@pbs:NoAccess
|
||||
acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
||||
"###)?;
|
||||
check_roles(&tree, "user1@pbs", "/", "Admin");
|
||||
check_roles(&tree, "user1@pbs", "/storage", "NoAccess");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store1", "DatastoreBackup");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store2", "Admin");
|
||||
check_roles(&tree, "user1@pbs", "/system", "Admin");
|
||||
check_roles(&tree, &user1, "/", "Admin");
|
||||
check_roles(&tree, &user1, "/storage", "NoAccess");
|
||||
check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
|
||||
check_roles(&tree, &user1, "/storage/store2", "Admin");
|
||||
check_roles(&tree, &user1, "/system", "Admin");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -758,13 +765,15 @@ acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
||||
|
||||
let mut tree = AclTree::new();
|
||||
|
||||
tree.insert_user_role("/", "user1@pbs", "Admin", true);
|
||||
tree.insert_user_role("/", "user1@pbs", "Audit", true);
|
||||
let user1: Userid = "user1@pbs".parse()?;
|
||||
|
||||
check_roles(&tree, "user1@pbs", "/", "Admin,Audit");
|
||||
tree.insert_user_role("/", &user1, "Admin", true);
|
||||
tree.insert_user_role("/", &user1, "Audit", true);
|
||||
|
||||
tree.insert_user_role("/", "user1@pbs", "NoAccess", true);
|
||||
check_roles(&tree, "user1@pbs", "/", "NoAccess");
|
||||
check_roles(&tree, &user1, "/", "Admin,Audit");
|
||||
|
||||
tree.insert_user_role("/", &user1, "NoAccess", true);
|
||||
check_roles(&tree, &user1, "/", "NoAccess");
|
||||
|
||||
let mut raw: Vec<u8> = Vec::new();
|
||||
tree.write_config(&mut raw)?;
|
||||
@ -780,20 +789,21 @@ acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
||||
|
||||
let mut tree = AclTree::new();
|
||||
|
||||
tree.insert_user_role("/storage", "user1@pbs", "NoAccess", true);
|
||||
let user1: Userid = "user1@pbs".parse()?;
|
||||
|
||||
check_roles(&tree, "user1@pbs", "/storage", "NoAccess");
|
||||
tree.insert_user_role("/storage", &user1, "NoAccess", true);
|
||||
|
||||
tree.insert_user_role("/storage", "user1@pbs", "Admin", true);
|
||||
tree.insert_user_role("/storage", "user1@pbs", "Audit", true);
|
||||
check_roles(&tree, &user1, "/storage", "NoAccess");
|
||||
|
||||
check_roles(&tree, "user1@pbs", "/storage", "Admin,Audit");
|
||||
tree.insert_user_role("/storage", &user1, "Admin", true);
|
||||
tree.insert_user_role("/storage", &user1, "Audit", true);
|
||||
|
||||
tree.insert_user_role("/storage", "user1@pbs", "NoAccess", true);
|
||||
check_roles(&tree, &user1, "/storage", "Admin,Audit");
|
||||
|
||||
check_roles(&tree, "user1@pbs", "/storage", "NoAccess");
|
||||
tree.insert_user_role("/storage", &user1, "NoAccess", true);
|
||||
|
||||
check_roles(&tree, &user1, "/storage", "NoAccess");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use proxmox::api::UserInformation;
|
||||
|
||||
use super::acl::{AclTree, ROLE_NAMES, ROLE_ADMIN};
|
||||
use super::user::User;
|
||||
use crate::api2::types::Userid;
|
||||
|
||||
/// Cache User/Group/Acl configuration data for fast permission tests
|
||||
pub struct CachedUserInfo {
|
||||
@ -57,8 +58,8 @@ impl CachedUserInfo {
|
||||
}
|
||||
|
||||
/// Test if a user account is enabled and not expired
|
||||
pub fn is_active_user(&self, userid: &str) -> bool {
|
||||
if let Ok(info) = self.user_cfg.lookup::<User>("user", &userid) {
|
||||
pub fn is_active_user(&self, userid: &Userid) -> bool {
|
||||
if let Ok(info) = self.user_cfg.lookup::<User>("user", userid.as_str()) {
|
||||
if !info.enable.unwrap_or(true) {
|
||||
return false;
|
||||
}
|
||||
@ -77,12 +78,12 @@ impl CachedUserInfo {
|
||||
|
||||
pub fn check_privs(
|
||||
&self,
|
||||
userid: &str,
|
||||
userid: &Userid,
|
||||
path: &[&str],
|
||||
required_privs: u64,
|
||||
partial: bool,
|
||||
) -> Result<(), Error> {
|
||||
let user_privs = self.lookup_privs(userid, path);
|
||||
let user_privs = self.lookup_privs(&userid, path);
|
||||
let allowed = if partial {
|
||||
(user_privs & required_privs) != 0
|
||||
} else {
|
||||
@ -97,18 +98,20 @@ impl CachedUserInfo {
|
||||
}
|
||||
}
|
||||
|
||||
impl UserInformation for CachedUserInfo {
|
||||
fn is_superuser(&self, userid: &str) -> bool {
|
||||
impl CachedUserInfo {
|
||||
pub fn is_superuser(&self, userid: &Userid) -> bool {
|
||||
userid == "root@pam"
|
||||
}
|
||||
|
||||
fn is_group_member(&self, _userid: &str, _group: &str) -> bool {
|
||||
pub fn is_group_member(&self, _userid: &Userid, _group: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn lookup_privs(&self, userid: &str, path: &[&str]) -> u64 {
|
||||
pub fn lookup_privs(&self, userid: &Userid, path: &[&str]) -> u64 {
|
||||
|
||||
if self.is_superuser(userid) { return ROLE_ADMIN; }
|
||||
if self.is_superuser(userid) {
|
||||
return ROLE_ADMIN;
|
||||
}
|
||||
|
||||
let roles = self.acl_tree.roles(userid, path);
|
||||
let mut privs: u64 = 0;
|
||||
@ -120,3 +123,20 @@ impl UserInformation for CachedUserInfo {
|
||||
privs
|
||||
}
|
||||
}
|
||||
|
||||
impl UserInformation for CachedUserInfo {
|
||||
fn is_superuser(&self, userid: &str) -> bool {
|
||||
userid == "root@pam"
|
||||
}
|
||||
|
||||
fn is_group_member(&self, _userid: &str, _group: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn lookup_privs(&self, userid: &str, path: &[&str]) -> u64 {
|
||||
match userid.parse::<Userid>() {
|
||||
Ok(userid) => Self::lookup_privs(self, &userid, path),
|
||||
Err(_) => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ pub const REMOTE_PASSWORD_SCHEMA: Schema = StringSchema::new("Password or auth t
|
||||
schema: DNS_NAME_OR_IP_SCHEMA,
|
||||
},
|
||||
userid: {
|
||||
schema: PROXMOX_USER_ID_SCHEMA,
|
||||
type: Userid,
|
||||
},
|
||||
password: {
|
||||
schema: REMOTE_PASSWORD_SCHEMA,
|
||||
@ -58,7 +58,7 @@ pub struct Remote {
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
pub comment: Option<String>,
|
||||
pub host: String,
|
||||
pub userid: String,
|
||||
pub userid: Userid,
|
||||
#[serde(skip_serializing_if="String::is_empty")]
|
||||
#[serde(with = "proxmox::tools::serde::string_as_base64")]
|
||||
pub password: String,
|
||||
|
@ -56,7 +56,7 @@ pub const EMAIL_SCHEMA: Schema = StringSchema::new("E-Mail Address.")
|
||||
#[api(
|
||||
properties: {
|
||||
userid: {
|
||||
schema: PROXMOX_USER_ID_SCHEMA,
|
||||
type: Userid,
|
||||
},
|
||||
comment: {
|
||||
optional: true,
|
||||
@ -87,7 +87,7 @@ pub const EMAIL_SCHEMA: Schema = StringSchema::new("E-Mail Address.")
|
||||
#[derive(Serialize,Deserialize)]
|
||||
/// User properties.
|
||||
pub struct User {
|
||||
pub userid: String,
|
||||
pub userid: Userid,
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
pub comment: Option<String>,
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
@ -109,7 +109,7 @@ fn init() -> SectionConfig {
|
||||
};
|
||||
|
||||
let plugin = SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), obj_schema);
|
||||
let mut config = SectionConfig::new(&PROXMOX_USER_ID_SCHEMA);
|
||||
let mut config = SectionConfig::new(&Userid::API_SCHEMA);
|
||||
|
||||
config.register_plugin(plugin);
|
||||
|
||||
@ -129,7 +129,7 @@ pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> {
|
||||
|
||||
if data.sections.get("root@pam").is_none() {
|
||||
let user: User = User {
|
||||
userid: "root@pam".to_string(),
|
||||
userid: Userid::root_userid().clone(),
|
||||
comment: Some("Superuser".to_string()),
|
||||
enable: None,
|
||||
expire: None,
|
||||
|
Reference in New Issue
Block a user