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 A10F862C65 for ; Wed, 28 Oct 2020 12:36:47 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 9E98B1E999 for ; Wed, 28 Oct 2020 12:36:47 +0100 (CET) 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 B57981E84B for ; Wed, 28 Oct 2020 12:36:45 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 81131459B0 for ; Wed, 28 Oct 2020 12:36:45 +0100 (CET) From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= To: pbs-devel@lists.proxmox.com Date: Wed, 28 Oct 2020 12:36:29 +0100 Message-Id: <20201028113632.814586-9-f.gruenbichler@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201028113632.814586-1-f.gruenbichler@proxmox.com> References: <20201028113632.814586-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.029 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 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [user.rs] Subject: [pbs-devel] [PATCH proxmox-backup 06/16] api: allow listing users + tokens 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: Wed, 28 Oct 2020 11:36:47 -0000 since it's not possible to extend existing structs, UserWithTokens duplicates most of user::User.. to avoid duplicating user::ApiToken as well, this returns full API token IDs, not just the token name part. Signed-off-by: Fabian Grünbichler --- src/api2/access/user.rs | 121 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 4 deletions(-) diff --git a/src/api2/access/user.rs b/src/api2/access/user.rs index 597ffeaf..a3d8da6c 100644 --- a/src/api2/access/user.rs +++ b/src/api2/access/user.rs @@ -1,5 +1,7 @@ use anyhow::{bail, Error}; +use serde::{Serialize, Deserialize}; use serde_json::{json, Value}; +use std::collections::HashMap; use std::convert::TryFrom; use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; @@ -19,9 +21,91 @@ pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.") .max_length(64) .schema(); +#[api( + properties: { + userid: { + type: Userid, + }, + comment: { + optional: true, + schema: SINGLE_LINE_COMMENT_SCHEMA, + }, + enable: { + optional: true, + schema: user::ENABLE_USER_SCHEMA, + }, + expire: { + optional: true, + schema: user::EXPIRE_USER_SCHEMA, + }, + firstname: { + optional: true, + schema: user::FIRST_NAME_SCHEMA, + }, + lastname: { + schema: user::LAST_NAME_SCHEMA, + optional: true, + }, + email: { + schema: user::EMAIL_SCHEMA, + optional: true, + }, + tokens: { + type: Array, + optional: true, + description: "List of user's API tokens.", + items: { + type: user::ApiToken + }, + }, + } +)] +#[derive(Serialize,Deserialize)] +/// User properties with added list of ApiTokens +pub struct UserWithTokens { + pub userid: 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, + #[serde(skip_serializing_if="Option::is_none")] + pub firstname: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub lastname: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub email: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub tokens: Option>, +} + +impl UserWithTokens { + fn new(user: user::User) -> Self { + Self { + userid: user.userid, + comment: user.comment, + enable: user.enable, + expire: user.expire, + firstname: user.firstname, + lastname: user.lastname, + email: user.email, + tokens: None, + } + } +} + + #[api( input: { - properties: {}, + properties: { + include_tokens: { + type: bool, + description: "Include user's API tokens in returned list.", + optional: true, + default: false, + }, + }, }, returns: { description: "List users (with config digest).", @@ -35,10 +119,10 @@ pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.") )] /// List users pub fn list_users( - _param: Value, + include_tokens: bool, _info: &ApiMethod, mut rpcenv: &mut dyn RpcEnvironment, -) -> Result, Error> { +) -> Result, Error> { let (config, digest) = user::config()?; @@ -55,11 +139,40 @@ pub fn list_users( top_level_allowed || user.userid == userid }; + let list:Vec = config.convert_to_typed_array("user")?; rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); - Ok(list.into_iter().filter(filter_by_privs).collect()) + let iter = list.into_iter().filter(filter_by_privs); + let list = if include_tokens { + let tokens:Vec = config.convert_to_typed_array("token")?; + let mut user_to_tokens = tokens + .into_iter() + .fold( + HashMap::new(), + |mut map: HashMap>, token: user::ApiToken| { + if token.tokenid.is_token() { + map + .entry(token.tokenid.user().clone()) + .or_default() + .push(token); + } + map + }); + iter + .map(|user: user::User| { + let mut user = UserWithTokens::new(user); + user.tokens = user_to_tokens.remove(&user.userid); + user + }) + .collect() + } else { + iter.map(|user: user::User| UserWithTokens::new(user)) + .collect() + }; + + Ok(list) } #[api( -- 2.20.1