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) server-digest SHA256) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 2076C6131E for ; Tue, 20 Oct 2020 12:10:53 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 0F408DDE3 for ; Tue, 20 Oct 2020 12:10:53 +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) server-digest SHA256) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 597F0DDD8 for ; Tue, 20 Oct 2020 12:10:52 +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 2379D45E2D for ; Tue, 20 Oct 2020 12:10:52 +0200 (CEST) Date: Tue, 20 Oct 2020 12:10:51 +0200 From: Wolfgang Bumiller To: Fabian =?utf-8?Q?Gr=C3=BCnbichler?= Cc: pbs-devel@lists.proxmox.com Message-ID: <20201020101051.l5aojdxuz5tglvna@olga.proxmox.com> References: <20201019073919.588521-1-f.gruenbichler@proxmox.com> <20201019073919.588521-10-f.gruenbichler@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <20201019073919.588521-10-f.gruenbichler@proxmox.com> User-Agent: NeoMutt/20180716 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.015 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: Re: [pbs-devel] [RFC proxmox-backup 09/15] 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: Tue, 20 Oct 2020 10:10:53 -0000 On Mon, Oct 19, 2020 at 09:39:13AM +0200, Fabian Grünbichler wrote: > 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 > --- > > Notes: > returning the full tokenid is a difference compared to PVE > > src/api2/access/user.rs | 115 ++++++++++++++++++++++++++++++++++++++-- > 1 file changed, 111 insertions(+), 4 deletions(-) > > diff --git a/src/api2/access/user.rs b/src/api2/access/user.rs > index 4197cf60..8a019e53 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::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: { > + schema: PROXMOX_USER_ID_SCHEMA, > + }, > + 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()?; > > @@ -52,11 +136,34 @@ 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")?; missing space after ':' ;-) > + let mut user_to_tokens = tokens.into_iter() > + .fold(HashMap::new(), |mut map: HashMap>, token: user::ApiToken| { too long (and .into_iter() should be on a new line) > + if let Ok(owner) = token.tokenid.owner() { > + map.entry(owner).or_insert_with(|| Vec::new()).push(token); The `or_insert_with(...)` could be `or_default()` > + } > + 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