replace Userid with Authid

in most generic places. this is accompanied by a change in
RpcEnvironment to purposefully break existing call sites.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
This commit is contained in:
Fabian Grünbichler
2020-10-23 13:33:21 +02:00
committed by Wolfgang Bumiller
parent e10c5c74f6
commit e6dc35acb8
43 changed files with 400 additions and 303 deletions

View File

@ -15,7 +15,7 @@ use proxmox::tools::{fs::replace_file, fs::CreateOptions};
use proxmox::constnamedbitmap;
use proxmox::api::{api, schema::*};
use crate::api2::types::Userid;
use crate::api2::types::{Authid,Userid};
// define Privilege bitfield
@ -231,7 +231,7 @@ pub struct AclTree {
}
pub struct AclTreeNode {
pub users: HashMap<Userid, HashMap<String, bool>>,
pub users: HashMap<Authid, HashMap<String, bool>>,
pub groups: HashMap<String, HashMap<String, bool>>,
pub children: BTreeMap<String, AclTreeNode>,
}
@ -246,21 +246,21 @@ impl AclTreeNode {
}
}
pub fn extract_roles(&self, user: &Userid, all: bool) -> HashSet<String> {
let user_roles = self.extract_user_roles(user, all);
pub fn extract_roles(&self, auth_id: &Authid, all: bool) -> HashSet<String> {
let user_roles = self.extract_user_roles(auth_id, all);
if !user_roles.is_empty() {
// user privs always override group privs
return user_roles
};
self.extract_group_roles(user, all)
self.extract_group_roles(auth_id.user(), all)
}
pub fn extract_user_roles(&self, user: &Userid, all: bool) -> HashSet<String> {
pub fn extract_user_roles(&self, auth_id: &Authid, all: bool) -> HashSet<String> {
let mut set = HashSet::new();
let roles = match self.users.get(user) {
let roles = match self.users.get(auth_id) {
Some(m) => m,
None => return set,
};
@ -312,8 +312,8 @@ impl AclTreeNode {
roles.remove(role);
}
pub fn delete_user_role(&mut self, userid: &Userid, role: &str) {
let roles = match self.users.get_mut(userid) {
pub fn delete_user_role(&mut self, auth_id: &Authid, role: &str) {
let roles = match self.users.get_mut(auth_id) {
Some(r) => r,
None => return,
};
@ -331,8 +331,8 @@ impl AclTreeNode {
}
}
pub fn insert_user_role(&mut self, user: Userid, role: String, propagate: bool) {
let map = self.users.entry(user).or_insert_with(|| HashMap::new());
pub fn insert_user_role(&mut self, auth_id: Authid, role: String, propagate: bool) {
let map = self.users.entry(auth_id).or_insert_with(|| HashMap::new());
if role == ROLE_NAME_NO_ACCESS {
map.clear();
map.insert(role, propagate);
@ -383,13 +383,13 @@ impl AclTree {
node.delete_group_role(group, role);
}
pub fn delete_user_role(&mut self, path: &str, userid: &Userid, role: &str) {
pub fn delete_user_role(&mut self, path: &str, auth_id: &Authid, role: &str) {
let path = split_acl_path(path);
let node = match self.get_node(&path) {
Some(n) => n,
None => return,
};
node.delete_user_role(userid, role);
node.delete_user_role(auth_id, role);
}
pub fn insert_group_role(&mut self, path: &str, group: &str, role: &str, propagate: bool) {
@ -398,10 +398,10 @@ impl AclTree {
node.insert_group_role(group.to_string(), role.to_string(), propagate);
}
pub fn insert_user_role(&mut self, path: &str, user: &Userid, role: &str, propagate: bool) {
pub fn insert_user_role(&mut self, path: &str, auth_id: &Authid, role: &str, propagate: bool) {
let path = split_acl_path(path);
let node = self.get_or_insert_node(&path);
node.insert_user_role(user.to_owned(), role.to_string(), propagate);
node.insert_user_role(auth_id.to_owned(), role.to_string(), propagate);
}
fn write_node_config(
@ -413,18 +413,18 @@ impl AclTree {
let mut role_ug_map0 = HashMap::new();
let mut role_ug_map1 = HashMap::new();
for (user, roles) in &node.users {
for (auth_id, roles) in &node.users {
// no need to save, because root is always 'Administrator'
if user == "root@pam" { continue; }
if !auth_id.is_token() && auth_id.user() == "root@pam" { continue; }
for (role, propagate) in roles {
let role = role.as_str();
let user = user.to_string();
let auth_id = auth_id.to_string();
if *propagate {
role_ug_map1.entry(role).or_insert_with(|| BTreeSet::new())
.insert(user);
.insert(auth_id);
} else {
role_ug_map0.entry(role).or_insert_with(|| BTreeSet::new())
.insert(user);
.insert(auth_id);
}
}
}
@ -576,10 +576,10 @@ impl AclTree {
Ok(tree)
}
pub fn roles(&self, userid: &Userid, path: &[&str]) -> HashSet<String> {
pub fn roles(&self, auth_id: &Authid, path: &[&str]) -> HashSet<String> {
let mut node = &self.root;
let mut role_set = node.extract_roles(userid, path.is_empty());
let mut role_set = node.extract_roles(auth_id, path.is_empty());
for (pos, comp) in path.iter().enumerate() {
let last_comp = (pos + 1) == path.len();
@ -587,7 +587,7 @@ impl AclTree {
Some(n) => n,
None => return role_set, // path not found
};
let new_set = node.extract_roles(userid, last_comp);
let new_set = node.extract_roles(auth_id, last_comp);
if !new_set.is_empty() {
// overwrite previous settings
role_set = new_set;
@ -675,22 +675,22 @@ mod test {
use anyhow::{Error};
use super::AclTree;
use crate::api2::types::Userid;
use crate::api2::types::Authid;
fn check_roles(
tree: &AclTree,
user: &Userid,
auth_id: &Authid,
path: &str,
expected_roles: &str,
) {
let path_vec = super::split_acl_path(path);
let mut roles = tree.roles(user, &path_vec)
let mut roles = tree.roles(auth_id, &path_vec)
.iter().map(|v| v.clone()).collect::<Vec<String>>();
roles.sort();
let roles = roles.join(",");
assert_eq!(roles, expected_roles, "\nat check_roles for '{}' on '{}'", user, path);
assert_eq!(roles, expected_roles, "\nat check_roles for '{}' on '{}'", auth_id, path);
}
#[test]
@ -721,13 +721,13 @@ acl:1:/storage:user1@pbs:Admin
acl:1:/storage/store1:user1@pbs:DatastoreBackup
acl:1:/storage/store2:user2@pbs:DatastoreBackup
"###)?;
let user1: Userid = "user1@pbs".parse()?;
let user1: Authid = "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");
let user2: Userid = "user2@pbs".parse()?;
let user2: Authid = "user2@pbs".parse()?;
check_roles(&tree, &user2, "/", "");
check_roles(&tree, &user2, "/storage", "");
check_roles(&tree, &user2, "/storage/store1", "");
@ -744,7 +744,7 @@ acl:1:/:user1@pbs:Admin
acl:1:/storage:user1@pbs:NoAccess
acl:1:/storage/store1:user1@pbs:DatastoreBackup
"###)?;
let user1: Userid = "user1@pbs".parse()?;
let user1: Authid = "user1@pbs".parse()?;
check_roles(&tree, &user1, "/", "Admin");
check_roles(&tree, &user1, "/storage", "NoAccess");
check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
@ -770,7 +770,7 @@ acl:1:/storage/store1:user1@pbs:DatastoreBackup
let mut tree = AclTree::new();
let user1: Userid = "user1@pbs".parse()?;
let user1: Authid = "user1@pbs".parse()?;
tree.insert_user_role("/", &user1, "Admin", true);
tree.insert_user_role("/", &user1, "Audit", true);
@ -794,7 +794,7 @@ acl:1:/storage/store1:user1@pbs:DatastoreBackup
let mut tree = AclTree::new();
let user1: Userid = "user1@pbs".parse()?;
let user1: Authid = "user1@pbs".parse()?;
tree.insert_user_role("/storage", &user1, "NoAccess", true);

View File

@ -9,10 +9,10 @@ use lazy_static::lazy_static;
use proxmox::api::UserInformation;
use super::acl::{AclTree, ROLE_NAMES, ROLE_ADMIN};
use super::user::User;
use crate::api2::types::Userid;
use super::user::{ApiToken, User};
use crate::api2::types::{Authid, Userid};
/// Cache User/Group/Acl configuration data for fast permission tests
/// Cache User/Group/Token/Acl configuration data for fast permission tests
pub struct CachedUserInfo {
user_cfg: Arc<SectionConfigData>,
acl_tree: Arc<AclTree>,
@ -57,8 +57,10 @@ impl CachedUserInfo {
Ok(config)
}
/// Test if a user account is enabled and not expired
pub fn is_active_user(&self, userid: &Userid) -> bool {
/// Test if a authentication id is enabled and not expired
pub fn is_active_auth_id(&self, auth_id: &Authid) -> bool {
let userid = auth_id.user();
if let Ok(info) = self.user_cfg.lookup::<User>("user", userid.as_str()) {
if !info.enable.unwrap_or(true) {
return false;
@ -68,24 +70,41 @@ impl CachedUserInfo {
return false;
}
}
return true;
} else {
return false;
}
if auth_id.is_token() {
if let Ok(info) = self.user_cfg.lookup::<ApiToken>("token", &auth_id.to_string()) {
if !info.enable.unwrap_or(true) {
return false;
}
if let Some(expire) = info.expire {
if expire > 0 && expire <= now() {
return false;
}
}
return true;
} else {
return false;
}
}
return true;
}
pub fn check_privs(
&self,
userid: &Userid,
auth_id: &Authid,
path: &[&str],
required_privs: u64,
partial: bool,
) -> Result<(), Error> {
let user_privs = self.lookup_privs(&userid, path);
let privs = self.lookup_privs(&auth_id, path);
let allowed = if partial {
(user_privs & required_privs) != 0
(privs & required_privs) != 0
} else {
(user_privs & required_privs) == required_privs
(privs & required_privs) == required_privs
};
if !allowed {
// printing the path doesn't leaks any information as long as we
@ -95,27 +114,33 @@ impl CachedUserInfo {
Ok(())
}
pub fn is_superuser(&self, userid: &Userid) -> bool {
userid == "root@pam"
pub fn is_superuser(&self, auth_id: &Authid) -> bool {
!auth_id.is_token() && auth_id.user() == "root@pam"
}
pub fn is_group_member(&self, _userid: &Userid, _group: &str) -> bool {
false
}
pub fn lookup_privs(&self, userid: &Userid, path: &[&str]) -> u64 {
if self.is_superuser(userid) {
pub fn lookup_privs(&self, auth_id: &Authid, path: &[&str]) -> u64 {
if self.is_superuser(auth_id) {
return ROLE_ADMIN;
}
let roles = self.acl_tree.roles(userid, path);
let roles = self.acl_tree.roles(auth_id, path);
let mut privs: u64 = 0;
for role in roles {
if let Some((role_privs, _)) = ROLE_NAMES.get(role.as_str()) {
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());
privs &= self.lookup_privs(&user_auth_id, path);
}
privs
}
}
@ -129,9 +154,9 @@ impl UserInformation for CachedUserInfo {
false
}
fn lookup_privs(&self, userid: &str, path: &[&str]) -> u64 {
match userid.parse::<Userid>() {
Ok(userid) => Self::lookup_privs(self, &userid, path),
fn lookup_privs(&self, auth_id: &str, path: &[&str]) -> u64 {
match auth_id.parse::<Authid>() {
Ok(auth_id) => Self::lookup_privs(self, &auth_id, path),
Err(_) => 0,
}
}

View File

@ -45,7 +45,7 @@ pub const REMOTE_PASSWORD_SCHEMA: Schema = StringSchema::new("Password or auth t
type: u16,
},
userid: {
type: Userid,
type: Authid,
},
password: {
schema: REMOTE_PASSWORD_SCHEMA,

View File

@ -52,6 +52,36 @@ pub const EMAIL_SCHEMA: Schema = StringSchema::new("E-Mail Address.")
.max_length(64)
.schema();
#[api(
properties: {
tokenid: {
schema: PROXMOX_TOKEN_ID_SCHEMA,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
enable: {
optional: true,
schema: ENABLE_USER_SCHEMA,
},
expire: {
optional: true,
schema: EXPIRE_USER_SCHEMA,
},
}
)]
#[derive(Serialize,Deserialize)]
/// ApiToken properties.
pub struct ApiToken {
pub tokenid: Authid,
#[serde(skip_serializing_if="Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
pub enable: Option<bool>,
#[serde(skip_serializing_if="Option::is_none")]
pub expire: Option<i64>,
}
#[api(
properties: {
@ -103,15 +133,21 @@ pub struct User {
}
fn init() -> SectionConfig {
let obj_schema = match User::API_SCHEMA {
Schema::Object(ref obj_schema) => obj_schema,
let mut config = SectionConfig::new(&Authid::API_SCHEMA);
let user_schema = match User::API_SCHEMA {
Schema::Object(ref user_schema) => user_schema,
_ => unreachable!(),
};
let user_plugin = SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), user_schema);
config.register_plugin(user_plugin);
let plugin = SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), obj_schema);
let mut config = SectionConfig::new(&Userid::API_SCHEMA);
config.register_plugin(plugin);
let token_schema = match ApiToken::API_SCHEMA {
Schema::Object(ref token_schema) => token_schema,
_ => unreachable!(),
};
let token_plugin = SectionConfigPlugin::new("token".to_string(), Some("tokenid".to_string()), token_schema);
config.register_plugin(token_plugin);
config
}
@ -206,9 +242,26 @@ pub fn save_config(config: &SectionConfigData) -> Result<(), Error> {
}
// shell completion helper
pub fn complete_user_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
pub fn complete_userid(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
match config() {
Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(),
Ok((data, _digest)) => {
data.sections.iter()
.filter_map(|(id, (section_type, _))| {
if section_type == "user" {
Some(id.to_string())
} else {
None
}
}).collect()
},
Err(_) => return vec![],
}
}
// shell completion helper
pub fn complete_authid(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
match config() {
Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(),
Err(_) => vec![],
}
}