api: allow listing users + tokens

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 <f.gruenbichler@proxmox.com>
This commit is contained in:
Fabian Grünbichler 2020-10-07 14:38:12 +02:00 committed by Wolfgang Bumiller
parent 942078c40b
commit 6746bbb1a2
1 changed files with 117 additions and 4 deletions

View File

@ -1,5 +1,7 @@
use anyhow::{bail, Error}; use anyhow::{bail, Error};
use serde::{Serialize, Deserialize};
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::collections::HashMap;
use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission};
use proxmox::api::router::SubdirMap; use proxmox::api::router::SubdirMap;
@ -18,9 +20,91 @@ pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
.max_length(64) .max_length(64)
.schema(); .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<String>,
#[serde(skip_serializing_if="Option::is_none")]
pub enable: Option<bool>,
#[serde(skip_serializing_if="Option::is_none")]
pub expire: Option<i64>,
#[serde(skip_serializing_if="Option::is_none")]
pub firstname: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
pub lastname: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
pub tokens: Option<Vec<user::ApiToken>>,
}
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( #[api(
input: { input: {
properties: {}, properties: {
include_tokens: {
type: bool,
description: "Include user's API tokens in returned list.",
optional: true,
default: false,
},
},
}, },
returns: { returns: {
description: "List users (with config digest).", description: "List users (with config digest).",
@ -34,10 +118,10 @@ pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
)] )]
/// List users /// List users
pub fn list_users( pub fn list_users(
_param: Value, include_tokens: bool,
_info: &ApiMethod, _info: &ApiMethod,
mut rpcenv: &mut dyn RpcEnvironment, mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<user::User>, Error> { ) -> Result<Vec<UserWithTokens>, Error> {
let (config, digest) = user::config()?; let (config, digest) = user::config()?;
@ -54,11 +138,40 @@ pub fn list_users(
top_level_allowed || user.userid == userid top_level_allowed || user.userid == userid
}; };
let list:Vec<user::User> = config.convert_to_typed_array("user")?; let list:Vec<user::User> = config.convert_to_typed_array("user")?;
rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); 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<user::ApiToken> = config.convert_to_typed_array("token")?;
let mut user_to_tokens = tokens
.into_iter()
.fold(
HashMap::new(),
|mut map: HashMap<Userid, Vec<user::ApiToken>>, 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( #[api(