1 use anyhow
::{bail, Error}
;
2 use serde
::{Serialize, Deserialize}
;
3 use serde_json
::{json, Value}
;
4 use std
::collections
::HashMap
;
6 use proxmox
::api
::{api, ApiMethod, Router, RpcEnvironment, Permission}
;
7 use proxmox
::api
::router
::SubdirMap
;
8 use proxmox
::api
::schema
::{Schema, StringSchema}
;
9 use proxmox
::tools
::fs
::open_file_locked
;
11 use crate::api2
::types
::*;
12 use crate::config
::user
;
13 use crate::config
::token_shadow
;
14 use crate::config
::acl
::{PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY}
;
15 use crate::config
::cached_user_info
::CachedUserInfo
;
17 pub const PBS_PASSWORD_SCHEMA
: Schema
= StringSchema
::new("User Password.")
18 .format(&PASSWORD_FORMAT
)
30 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
34 schema
: user
::ENABLE_USER_SCHEMA
,
38 schema
: user
::EXPIRE_USER_SCHEMA
,
42 schema
: user
::FIRST_NAME_SCHEMA
,
45 schema
: user
::LAST_NAME_SCHEMA
,
49 schema
: user
::EMAIL_SCHEMA
,
55 description
: "List of user's API tokens.",
62 #[derive(Serialize,Deserialize)]
63 /// User properties with added list of ApiTokens
64 pub struct UserWithTokens
{
66 #[serde(skip_serializing_if="Option::is_none")]
67 pub comment
: Option
<String
>,
68 #[serde(skip_serializing_if="Option::is_none")]
69 pub enable
: Option
<bool
>,
70 #[serde(skip_serializing_if="Option::is_none")]
71 pub expire
: Option
<i64>,
72 #[serde(skip_serializing_if="Option::is_none")]
73 pub firstname
: Option
<String
>,
74 #[serde(skip_serializing_if="Option::is_none")]
75 pub lastname
: Option
<String
>,
76 #[serde(skip_serializing_if="Option::is_none")]
77 pub email
: Option
<String
>,
78 #[serde(skip_serializing_if="Vec::is_empty")]
79 pub tokens
: Vec
<user
::ApiToken
>,
83 fn new(user
: user
::User
) -> Self {
86 comment
: user
.comment
,
89 firstname
: user
.firstname
,
90 lastname
: user
.lastname
,
103 description
: "Include user's API tokens in returned list.",
110 description
: "List users (with config digest).",
112 items
: { type: user::User }
,
115 permission
: &Permission
::Anybody
,
116 description
: "Returns all or just the logged-in user, depending on privileges.",
121 include_tokens
: bool
,
123 mut rpcenv
: &mut dyn RpcEnvironment
,
124 ) -> Result
<Vec
<UserWithTokens
>, Error
> {
126 let (config
, digest
) = user
::config()?
;
128 // intentionally user only for now
129 let userid
: Userid
= rpcenv
.get_auth_id().unwrap().parse()?
;
130 let auth_id
= Authid
::from(userid
.clone());
132 let user_info
= CachedUserInfo
::new()?
;
134 let top_level_privs
= user_info
.lookup_privs(&auth_id
, &["access", "users"]);
135 let top_level_allowed
= (top_level_privs
& PRIV_SYS_AUDIT
) != 0;
137 let filter_by_privs
= |user
: &user
::User
| {
138 top_level_allowed
|| user
.userid
== userid
142 let list
:Vec
<user
::User
> = config
.convert_to_typed_array("user")?
;
144 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
146 let iter
= list
.into_iter().filter(filter_by_privs
);
147 let list
= if include_tokens
{
148 let tokens
: Vec
<user
::ApiToken
> = config
.convert_to_typed_array("token")?
;
149 let mut user_to_tokens
= tokens
153 |mut map
: HashMap
<Userid
, Vec
<user
::ApiToken
>>, token
: user
::ApiToken
| {
154 if token
.tokenid
.is_token() {
156 .entry(token
.tokenid
.user().clone())
163 .map(|user
: user
::User
| {
164 let mut user
= UserWithTokens
::new(user
);
165 user
.tokens
= user_to_tokens
.remove(&user
.userid
).unwrap_or_default();
170 iter
.map(|user
: user
::User
| UserWithTokens
::new(user
))
185 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
189 schema
: PBS_PASSWORD_SCHEMA
,
193 schema
: user
::ENABLE_USER_SCHEMA
,
197 schema
: user
::EXPIRE_USER_SCHEMA
,
201 schema
: user
::FIRST_NAME_SCHEMA
,
205 schema
: user
::LAST_NAME_SCHEMA
,
209 schema
: user
::EMAIL_SCHEMA
,
215 permission
: &Permission
::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY
, false),
219 pub fn create_user(password
: Option
<String
>, param
: Value
) -> Result
<(), Error
> {
221 let _lock
= open_file_locked(user
::USER_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
223 let user
: user
::User
= serde_json
::from_value(param
)?
;
225 let (mut config
, _digest
) = user
::config()?
;
227 if let Some(_
) = config
.sections
.get(user
.userid
.as_str()) {
228 bail
!("user '{}' already exists.", user
.userid
);
231 let authenticator
= crate::auth
::lookup_authenticator(&user
.userid
.realm())?
;
233 config
.set_data(user
.userid
.as_str(), "user", &user
)?
;
235 user
::save_config(&config
)?
;
237 if let Some(password
) = password
{
238 authenticator
.store_password(user
.userid
.name(), &password
)?
;
253 description
: "The user configuration (with config digest).",
257 permission
: &Permission
::Or(&[
258 &Permission
::Privilege(&["access", "users"], PRIV_SYS_AUDIT
, false),
259 &Permission
::UserParam("userid"),
263 /// Read user configuration data.
264 pub fn read_user(userid
: Userid
, mut rpcenv
: &mut dyn RpcEnvironment
) -> Result
<user
::User
, Error
> {
265 let (config
, digest
) = user
::config()?
;
266 let user
= config
.lookup("user", userid
.as_str())?
;
267 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
280 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
283 schema
: PBS_PASSWORD_SCHEMA
,
287 schema
: user
::ENABLE_USER_SCHEMA
,
291 schema
: user
::EXPIRE_USER_SCHEMA
,
295 schema
: user
::FIRST_NAME_SCHEMA
,
299 schema
: user
::LAST_NAME_SCHEMA
,
303 schema
: user
::EMAIL_SCHEMA
,
308 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
313 permission
: &Permission
::Or(&[
314 &Permission
::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY
, false),
315 &Permission
::UserParam("userid"),
319 /// Update user configuration.
322 comment
: Option
<String
>,
323 enable
: Option
<bool
>,
325 password
: Option
<String
>,
326 firstname
: Option
<String
>,
327 lastname
: Option
<String
>,
328 email
: Option
<String
>,
329 digest
: Option
<String
>,
330 ) -> Result
<(), Error
> {
332 let _lock
= open_file_locked(user
::USER_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
334 let (mut config
, expected_digest
) = user
::config()?
;
336 if let Some(ref digest
) = digest
{
337 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
338 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
341 let mut data
: user
::User
= config
.lookup("user", userid
.as_str())?
;
343 if let Some(comment
) = comment
{
344 let comment
= comment
.trim().to_string();
345 if comment
.is_empty() {
348 data
.comment
= Some(comment
);
352 if let Some(enable
) = enable
{
353 data
.enable
= if enable { None }
else { Some(false) }
;
356 if let Some(expire
) = expire
{
357 data
.expire
= if expire
> 0 { Some(expire) }
else { None }
;
360 if let Some(password
) = password
{
361 let authenticator
= crate::auth
::lookup_authenticator(userid
.realm())?
;
362 authenticator
.store_password(userid
.name(), &password
)?
;
365 if let Some(firstname
) = firstname
{
366 data
.firstname
= if firstname
.is_empty() { None }
else { Some(firstname) }
;
369 if let Some(lastname
) = lastname
{
370 data
.lastname
= if lastname
.is_empty() { None }
else { Some(lastname) }
;
372 if let Some(email
) = email
{
373 data
.email
= if email
.is_empty() { None }
else { Some(email) }
;
376 config
.set_data(userid
.as_str(), "user", &data
)?
;
378 user
::save_config(&config
)?
;
392 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
397 permission
: &Permission
::Or(&[
398 &Permission
::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY
, false),
399 &Permission
::UserParam("userid"),
403 /// Remove a user from the configuration file.
404 pub fn delete_user(userid
: Userid
, digest
: Option
<String
>) -> Result
<(), Error
> {
406 let _lock
= open_file_locked(user
::USER_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
408 let (mut config
, expected_digest
) = user
::config()?
;
410 if let Some(ref digest
) = digest
{
411 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
412 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
415 match config
.sections
.get(userid
.as_str()) {
416 Some(_
) => { config.sections.remove(userid.as_str()); }
,
417 None
=> bail
!("user '{}' does not exist.", userid
),
420 user
::save_config(&config
)?
;
437 description
: "Get API token metadata (with config digest).",
438 type: user
::ApiToken
,
441 permission
: &Permission
::Or(&[
442 &Permission
::Privilege(&["access", "users"], PRIV_SYS_AUDIT
, false),
443 &Permission
::UserParam("userid"),
447 /// Read user's API token metadata
450 tokenname
: Tokenname
,
452 mut rpcenv
: &mut dyn RpcEnvironment
,
453 ) -> Result
<user
::ApiToken
, Error
> {
455 let (config
, digest
) = user
::config()?
;
457 let tokenid
= Authid
::from((userid
, Some(tokenname
)));
459 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
460 config
.lookup("token", &tokenid
.to_string())
475 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
478 schema
: user
::ENABLE_USER_SCHEMA
,
482 schema
: user
::EXPIRE_USER_SCHEMA
,
487 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
492 permission
: &Permission
::Or(&[
493 &Permission
::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY
, false),
494 &Permission
::UserParam("userid"),
498 description
: "API token identifier + generated secret.",
502 description
: "The API token secret",
506 description
: "The API token identifier",
511 /// Generate a new API token with given metadata
512 pub fn generate_token(
514 tokenname
: Tokenname
,
515 comment
: Option
<String
>,
516 enable
: Option
<bool
>,
518 digest
: Option
<String
>,
519 ) -> Result
<Value
, Error
> {
521 let _lock
= open_file_locked(user
::USER_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
523 let (mut config
, expected_digest
) = user
::config()?
;
525 if let Some(ref digest
) = digest
{
526 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
527 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
530 let tokenid
= Authid
::from((userid
.clone(), Some(tokenname
.clone())));
531 let tokenid_string
= tokenid
.to_string();
533 if let Some(_
) = config
.sections
.get(&tokenid_string
) {
534 bail
!("token '{}' for user '{}' already exists.", tokenname
.as_str(), userid
);
537 let secret
= format
!("{:x}", proxmox
::tools
::uuid
::Uuid
::generate());
538 token_shadow
::set_secret(&tokenid
, &secret
)?
;
540 let token
= user
::ApiToken
{
541 tokenid
: tokenid
.clone(),
547 config
.set_data(&tokenid_string
, "token", &token
)?
;
549 user
::save_config(&config
)?
;
552 "tokenid": tokenid_string
,
569 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
572 schema
: user
::ENABLE_USER_SCHEMA
,
576 schema
: user
::EXPIRE_USER_SCHEMA
,
581 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
586 permission
: &Permission
::Or(&[
587 &Permission
::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY
, false),
588 &Permission
::UserParam("userid"),
592 /// Update user's API token metadata
595 tokenname
: Tokenname
,
596 comment
: Option
<String
>,
597 enable
: Option
<bool
>,
599 digest
: Option
<String
>,
600 ) -> Result
<(), Error
> {
602 let _lock
= open_file_locked(user
::USER_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
604 let (mut config
, expected_digest
) = user
::config()?
;
606 if let Some(ref digest
) = digest
{
607 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
608 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
611 let tokenid
= Authid
::from((userid
, Some(tokenname
)));
612 let tokenid_string
= tokenid
.to_string();
614 let mut data
: user
::ApiToken
= config
.lookup("token", &tokenid_string
)?
;
616 if let Some(comment
) = comment
{
617 let comment
= comment
.trim().to_string();
618 if comment
.is_empty() {
621 data
.comment
= Some(comment
);
625 if let Some(enable
) = enable
{
626 data
.enable
= if enable { None }
else { Some(false) }
;
629 if let Some(expire
) = expire
{
630 data
.expire
= if expire
> 0 { Some(expire) }
else { None }
;
633 config
.set_data(&tokenid_string
, "token", &data
)?
;
635 user
::save_config(&config
)?
;
652 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
657 permission
: &Permission
::Or(&[
658 &Permission
::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY
, false),
659 &Permission
::UserParam("userid"),
663 /// Delete a user's API token
666 tokenname
: Tokenname
,
667 digest
: Option
<String
>,
668 ) -> Result
<(), Error
> {
670 let _lock
= open_file_locked(user
::USER_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
672 let (mut config
, expected_digest
) = user
::config()?
;
674 if let Some(ref digest
) = digest
{
675 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
676 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
679 let tokenid
= Authid
::from((userid
.clone(), Some(tokenname
.clone())));
680 let tokenid_string
= tokenid
.to_string();
682 match config
.sections
.get(&tokenid_string
) {
683 Some(_
) => { config.sections.remove(&tokenid_string); }
,
684 None
=> bail
!("token '{}' of user '{}' does not exist.", tokenname
.as_str(), userid
),
687 token_shadow
::delete_secret(&tokenid
)?
;
689 user
::save_config(&config
)?
;
703 description
: "List user's API tokens (with config digest).",
705 items
: { type: user::ApiToken }
,
708 permission
: &Permission
::Or(&[
709 &Permission
::Privilege(&["access", "users"], PRIV_SYS_AUDIT
, false),
710 &Permission
::UserParam("userid"),
714 /// List user's API tokens
718 mut rpcenv
: &mut dyn RpcEnvironment
,
719 ) -> Result
<Vec
<user
::ApiToken
>, Error
> {
721 let (config
, digest
) = user
::config()?
;
723 let list
:Vec
<user
::ApiToken
> = config
.convert_to_typed_array("token")?
;
725 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
727 let filter_by_owner
= |token
: &user
::ApiToken
| {
728 if token
.tokenid
.is_token() {
729 token
.tokenid
.user() == &userid
735 Ok(list
.into_iter().filter(filter_by_owner
).collect())
738 const TOKEN_ITEM_ROUTER
: Router
= Router
::new()
739 .get(&API_METHOD_READ_TOKEN
)
740 .put(&API_METHOD_UPDATE_TOKEN
)
741 .post(&API_METHOD_GENERATE_TOKEN
)
742 .delete(&API_METHOD_DELETE_TOKEN
);
744 const TOKEN_ROUTER
: Router
= Router
::new()
745 .get(&API_METHOD_LIST_TOKENS
)
746 .match_all("tokenname", &TOKEN_ITEM_ROUTER
);
748 const USER_SUBDIRS
: SubdirMap
= &[
749 ("token", &TOKEN_ROUTER
),
752 const USER_ROUTER
: Router
= Router
::new()
753 .get(&API_METHOD_READ_USER
)
754 .put(&API_METHOD_UPDATE_USER
)
755 .delete(&API_METHOD_DELETE_USER
)
756 .subdirs(USER_SUBDIRS
);
758 pub const ROUTER
: Router
= Router
::new()
759 .get(&API_METHOD_LIST_USERS
)
760 .post(&API_METHOD_CREATE_USER
)
761 .match_all("userid", &USER_ROUTER
);