From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 8B4C160966 for ; Mon, 19 Oct 2020 09:40:28 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 85F3729828 for ; Mon, 19 Oct 2020 09:39:42 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id CD4EB2981E for ; Mon, 19 Oct 2020 09:39:41 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 93D0645D69 for ; Mon, 19 Oct 2020 09:39:41 +0200 (CEST) From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= To: pbs-devel@lists.proxmox.com Date: Mon, 19 Oct 2020 09:39:09 +0200 Message-Id: <20201019073919.588521-6-f.gruenbichler@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201019073919.588521-1-f.gruenbichler@proxmox.com> References: <20201019073919.588521-1-f.gruenbichler@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.032 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pbs-devel] [RFC proxmox-backup 05/15] add ApiToken to user.cfg and CachedUserInfo X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 19 Oct 2020 07:40:28 -0000 Signed-off-by: Fabian Grünbichler --- src/bin/proxmox-backup-client.rs | 4 +- src/bin/proxmox_backup_manager/acl.rs | 2 +- src/config/cached_user_info.rs | 48 +++++++++++++++---- src/config/user.rs | 67 ++++++++++++++++++++++++--- 4 files changed, 102 insertions(+), 19 deletions(-) diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index 1fbbca09..bf336243 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -36,7 +36,7 @@ use proxmox_backup::api2::types::*; use proxmox_backup::api2::version; use proxmox_backup::client::*; use proxmox_backup::pxar::catalog::*; -use proxmox_backup::config::user::complete_user_name; +use proxmox_backup::config::user::complete_userid; use proxmox_backup::backup::{ archive_type, decrypt_key, @@ -2010,7 +2010,7 @@ fn main() { let change_owner_cmd_def = CliCommand::new(&API_METHOD_CHANGE_BACKUP_OWNER) .arg_param(&["group", "new-owner"]) .completion_cb("group", complete_backup_group) - .completion_cb("new-owner", complete_user_name) + .completion_cb("new-owner", complete_userid) .completion_cb("repository", complete_repository); let cmd_def = CliCommandMap::new() diff --git a/src/bin/proxmox_backup_manager/acl.rs b/src/bin/proxmox_backup_manager/acl.rs index bc2e8f7a..3fbb3bcb 100644 --- a/src/bin/proxmox_backup_manager/acl.rs +++ b/src/bin/proxmox_backup_manager/acl.rs @@ -60,7 +60,7 @@ pub fn acl_commands() -> CommandLineInterface { "update", CliCommand::new(&api2::access::acl::API_METHOD_UPDATE_ACL) .arg_param(&["path", "role"]) - .completion_cb("userid", config::user::complete_user_name) + .completion_cb("userid", config::user::complete_userid) .completion_cb("path", config::datastore::complete_acl_path) ); diff --git a/src/config/cached_user_info.rs b/src/config/cached_user_info.rs index cf9c534d..93f360c8 100644 --- a/src/config/cached_user_info.rs +++ b/src/config/cached_user_info.rs @@ -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 super::user::{ApiToken, User}; use crate::api2::types::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, acl_tree: Arc, @@ -57,20 +57,44 @@ impl CachedUserInfo { Ok(config) } - /// Test if a user account is enabled and not expired + /// Test if a userid is enabled and not expired pub fn is_active_user(&self, userid: &Userid) -> bool { - if let Ok(info) = self.user_cfg.lookup::("user", userid.as_str()) { - if !info.enable.unwrap_or(true) { + if userid.is_tokenid() { + if let Ok(owner) = userid.owner() { + if !self.is_active_user(&owner) { + return false; + } + } else { return false; } - if let Some(expire) = info.expire { - if expire > 0 && expire <= now() { + + if let Ok(info) = self.user_cfg.lookup::("token", userid.as_str()) { + 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; } else { - return false; + if let Ok(info) = self.user_cfg.lookup::("user", userid.as_str()) { + 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; + } } } @@ -116,6 +140,12 @@ impl CachedUserInfo { privs |= role_privs; } } + + if userid.is_tokenid() { + // limit privs to that of owning user + privs &= self.lookup_privs(&userid.owner().unwrap(), path); + } + privs } } diff --git a/src/config/user.rs b/src/config/user.rs index efb346d8..5ebb9f99 100644 --- a/src/config/user.rs +++ b/src/config/user.rs @@ -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: Userid, + #[serde(skip_serializing_if="Option::is_none")] + pub comment: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub enable: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub expire: Option, +} #[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(&PROXMOX_USER_OR_TOKEN_ID_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(&PROXMOX_USER_ID_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 } @@ -208,7 +244,24 @@ pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { // shell completion helper pub fn complete_user_name(_arg: &str, _param: &HashMap) -> Vec { 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_userid(_arg: &str, _param: &HashMap) -> Vec { + match config() { + Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(), + Err(_) => vec![], + } +} -- 2.20.1