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:
		
				
					committed by
					
						 Wolfgang Bumiller
						Wolfgang Bumiller
					
				
			
			
				
	
			
			
			
						parent
						
							942078c40b
						
					
				
				
					commit
					6746bbb1a2
				
			| @ -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( | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user