3 use anyhow
::{bail, format_err, Error}
;
4 use serde
::{Serialize, Deserialize}
;
5 use serde_json
::{json, Value}
;
6 use std
::collections
::HashMap
;
8 use proxmox
::api
::{api, ApiMethod, Router, RpcEnvironment, Permission}
;
9 use proxmox
::api
::router
::SubdirMap
;
10 use proxmox
::api
::schema
::{Schema, StringSchema}
;
13 PASSWORD_FORMAT
, PROXMOX_CONFIG_DIGEST_SCHEMA
, SINGLE_LINE_COMMENT_SCHEMA
, Authid
,
14 Tokenname
, UserWithTokens
, Userid
,
17 use crate::config
::user
;
18 use crate::config
::token_shadow
;
19 use crate::config
::acl
::{PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY}
;
20 use crate::config
::cached_user_info
::CachedUserInfo
;
21 use pbs_config
::open_backup_lockfile
;
23 pub const PBS_PASSWORD_SCHEMA
: Schema
= StringSchema
::new("User Password.")
24 .format(&PASSWORD_FORMAT
)
29 fn new_user_with_tokens(user
: user
::User
) -> UserWithTokens
{
32 comment
: user
.comment
,
35 firstname
: user
.firstname
,
36 lastname
: user
.lastname
,
47 description
: "Include user's API tokens in returned list.",
54 description
: "List users (with config digest).",
56 items
: { type: UserWithTokens }
,
59 permission
: &Permission
::Anybody
,
60 description
: "Returns all or just the logged-in user (/API token owner), depending on privileges.",
67 mut rpcenv
: &mut dyn RpcEnvironment
,
68 ) -> Result
<Vec
<UserWithTokens
>, Error
> {
70 let (config
, digest
) = user
::config()?
;
72 let auth_id
: Authid
= rpcenv
74 .ok_or_else(|| format_err
!("no authid available"))?
77 let userid
= auth_id
.user();
79 let user_info
= CachedUserInfo
::new()?
;
81 let top_level_privs
= user_info
.lookup_privs(&auth_id
, &["access", "users"]);
82 let top_level_allowed
= (top_level_privs
& PRIV_SYS_AUDIT
) != 0;
84 let filter_by_privs
= |user
: &user
::User
| {
85 top_level_allowed
|| user
.userid
== *userid
89 let list
:Vec
<user
::User
> = config
.convert_to_typed_array("user")?
;
91 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
93 let iter
= list
.into_iter().filter(filter_by_privs
);
94 let list
= if include_tokens
{
95 let tokens
: Vec
<user
::ApiToken
> = config
.convert_to_typed_array("token")?
;
96 let mut user_to_tokens
= tokens
100 |mut map
: HashMap
<Userid
, Vec
<user
::ApiToken
>>, token
: user
::ApiToken
| {
101 if token
.tokenid
.is_token() {
103 .entry(token
.tokenid
.user().clone())
110 .map(|user
: user
::User
| {
111 let mut user
= new_user_with_tokens(user
);
112 user
.tokens
= user_to_tokens
.remove(&user
.userid
).unwrap_or_default();
117 iter
.map(new_user_with_tokens
)
132 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
136 schema
: PBS_PASSWORD_SCHEMA
,
140 schema
: user
::ENABLE_USER_SCHEMA
,
144 schema
: user
::EXPIRE_USER_SCHEMA
,
148 schema
: user
::FIRST_NAME_SCHEMA
,
152 schema
: user
::LAST_NAME_SCHEMA
,
156 schema
: user
::EMAIL_SCHEMA
,
162 permission
: &Permission
::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY
, false),
167 password
: Option
<String
>,
169 rpcenv
: &mut dyn RpcEnvironment
170 ) -> Result
<(), Error
> {
172 let _lock
= open_backup_lockfile(user
::USER_CFG_LOCKFILE
, None
, true)?
;
174 let user
: user
::User
= serde_json
::from_value(param
)?
;
176 let (mut config
, _digest
) = user
::config()?
;
178 if config
.sections
.get(user
.userid
.as_str()).is_some() {
179 bail
!("user '{}' already exists.", user
.userid
);
182 config
.set_data(user
.userid
.as_str(), "user", &user
)?
;
184 let realm
= user
.userid
.realm();
186 // Fails if realm does not exist!
187 let authenticator
= crate::auth
::lookup_authenticator(realm
)?
;
189 user
::save_config(&config
)?
;
191 if let Some(password
) = password
{
192 let user_info
= CachedUserInfo
::new()?
;
193 let current_auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
194 if realm
== "pam" && !user_info
.is_superuser(¤t_auth_id
) {
195 bail
!("only superuser can edit pam credentials!");
197 authenticator
.store_password(user
.userid
.name(), &password
)?
;
211 returns
: { type: user::User }
,
213 permission
: &Permission
::Or(&[
214 &Permission
::Privilege(&["access", "users"], PRIV_SYS_AUDIT
, false),
215 &Permission
::UserParam("userid"),
219 /// Read user configuration data.
220 pub fn read_user(userid
: Userid
, mut rpcenv
: &mut dyn RpcEnvironment
) -> Result
<user
::User
, Error
> {
221 let (config
, digest
) = user
::config()?
;
222 let user
= config
.lookup("user", userid
.as_str())?
;
223 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
228 #[derive(Serialize, Deserialize)]
229 #[serde(rename_all="kebab-case")]
230 #[allow(non_camel_case_types)]
231 pub enum DeletableProperty
{
232 /// Delete the comment property.
234 /// Delete the firstname property.
236 /// Delete the lastname property.
238 /// Delete the email property.
251 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
254 schema
: PBS_PASSWORD_SCHEMA
,
258 schema
: user
::ENABLE_USER_SCHEMA
,
262 schema
: user
::EXPIRE_USER_SCHEMA
,
266 schema
: user
::FIRST_NAME_SCHEMA
,
270 schema
: user
::LAST_NAME_SCHEMA
,
274 schema
: user
::EMAIL_SCHEMA
,
278 description
: "List of properties to delete.",
282 type: DeletableProperty
,
287 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
292 permission
: &Permission
::Or(&[
293 &Permission
::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY
, false),
294 &Permission
::UserParam("userid"),
298 /// Update user configuration.
299 #[allow(clippy::too_many_arguments)]
302 comment
: Option
<String
>,
303 enable
: Option
<bool
>,
305 password
: Option
<String
>,
306 firstname
: Option
<String
>,
307 lastname
: Option
<String
>,
308 email
: Option
<String
>,
309 delete
: Option
<Vec
<DeletableProperty
>>,
310 digest
: Option
<String
>,
311 rpcenv
: &mut dyn RpcEnvironment
,
312 ) -> Result
<(), Error
> {
314 let _lock
= open_backup_lockfile(user
::USER_CFG_LOCKFILE
, None
, true)?
;
316 let (mut config
, expected_digest
) = user
::config()?
;
318 if let Some(ref digest
) = digest
{
319 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
320 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
323 let mut data
: user
::User
= config
.lookup("user", userid
.as_str())?
;
325 if let Some(delete
) = delete
{
326 for delete_prop
in delete
{
328 DeletableProperty
::comment
=> data
.comment
= None
,
329 DeletableProperty
::firstname
=> data
.firstname
= None
,
330 DeletableProperty
::lastname
=> data
.lastname
= None
,
331 DeletableProperty
::email
=> data
.email
= None
,
336 if let Some(comment
) = comment
{
337 let comment
= comment
.trim().to_string();
338 if comment
.is_empty() {
341 data
.comment
= Some(comment
);
345 if let Some(enable
) = enable
{
346 data
.enable
= if enable { None }
else { Some(false) }
;
349 if let Some(expire
) = expire
{
350 data
.expire
= if expire
> 0 { Some(expire) }
else { None }
;
353 if let Some(password
) = password
{
354 let user_info
= CachedUserInfo
::new()?
;
355 let current_auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
356 let self_service
= current_auth_id
.user() == &userid
;
357 let target_realm
= userid
.realm();
358 if !self_service
&& target_realm
== "pam" && !user_info
.is_superuser(¤t_auth_id
) {
359 bail
!("only superuser can edit pam credentials!");
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 _tfa_lock
= crate::config
::tfa
::write_lock()?
;
407 let _lock
= open_backup_lockfile(user
::USER_CFG_LOCKFILE
, None
, true)?
;
409 let (mut config
, expected_digest
) = user
::config()?
;
411 if let Some(ref digest
) = digest
{
412 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
413 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
416 match config
.sections
.get(userid
.as_str()) {
417 Some(_
) => { config.sections.remove(userid.as_str()); }
,
418 None
=> bail
!("user '{}' does not exist.", userid
),
421 user
::save_config(&config
)?
;
423 let authenticator
= crate::auth
::lookup_authenticator(userid
.realm())?
;
424 match authenticator
.remove_password(userid
.name()) {
428 "error removing password after deleting user {:?}: {}",
434 match crate::config
::tfa
::read().and_then(|mut cfg
| {
435 let _
: bool
= cfg
.remove_user(&userid
);
436 crate::config
::tfa
::write(&cfg
)
441 "error updating TFA config after deleting user {:?}: {}",
461 returns
: { type: user::ApiToken }
,
463 permission
: &Permission
::Or(&[
464 &Permission
::Privilege(&["access", "users"], PRIV_SYS_AUDIT
, false),
465 &Permission
::UserParam("userid"),
469 /// Read user's API token metadata
472 tokenname
: Tokenname
,
474 mut rpcenv
: &mut dyn RpcEnvironment
,
475 ) -> Result
<user
::ApiToken
, Error
> {
477 let (config
, digest
) = user
::config()?
;
479 let tokenid
= Authid
::from((userid
, Some(tokenname
)));
481 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
482 config
.lookup("token", &tokenid
.to_string())
497 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
500 schema
: user
::ENABLE_USER_SCHEMA
,
504 schema
: user
::EXPIRE_USER_SCHEMA
,
509 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
514 permission
: &Permission
::Or(&[
515 &Permission
::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY
, false),
516 &Permission
::UserParam("userid"),
520 description
: "API token identifier + generated secret.",
524 description
: "The API token secret",
528 description
: "The API token identifier",
533 /// Generate a new API token with given metadata
534 pub fn generate_token(
536 tokenname
: Tokenname
,
537 comment
: Option
<String
>,
538 enable
: Option
<bool
>,
540 digest
: Option
<String
>,
541 ) -> Result
<Value
, Error
> {
543 let _lock
= open_backup_lockfile(user
::USER_CFG_LOCKFILE
, None
, true)?
;
545 let (mut config
, expected_digest
) = user
::config()?
;
547 if let Some(ref digest
) = digest
{
548 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
549 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
552 let tokenid
= Authid
::from((userid
.clone(), Some(tokenname
.clone())));
553 let tokenid_string
= tokenid
.to_string();
555 if config
.sections
.get(&tokenid_string
).is_some() {
556 bail
!("token '{}' for user '{}' already exists.", tokenname
.as_str(), userid
);
559 let secret
= format
!("{:x}", proxmox
::tools
::uuid
::Uuid
::generate());
560 token_shadow
::set_secret(&tokenid
, &secret
)?
;
562 let token
= user
::ApiToken
{
569 config
.set_data(&tokenid_string
, "token", &token
)?
;
571 user
::save_config(&config
)?
;
574 "tokenid": tokenid_string
,
591 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
594 schema
: user
::ENABLE_USER_SCHEMA
,
598 schema
: user
::EXPIRE_USER_SCHEMA
,
603 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
608 permission
: &Permission
::Or(&[
609 &Permission
::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY
, false),
610 &Permission
::UserParam("userid"),
614 /// Update user's API token metadata
617 tokenname
: Tokenname
,
618 comment
: Option
<String
>,
619 enable
: Option
<bool
>,
621 digest
: Option
<String
>,
622 ) -> Result
<(), Error
> {
624 let _lock
= open_backup_lockfile(user
::USER_CFG_LOCKFILE
, None
, true)?
;
626 let (mut config
, expected_digest
) = user
::config()?
;
628 if let Some(ref digest
) = digest
{
629 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
630 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
633 let tokenid
= Authid
::from((userid
, Some(tokenname
)));
634 let tokenid_string
= tokenid
.to_string();
636 let mut data
: user
::ApiToken
= config
.lookup("token", &tokenid_string
)?
;
638 if let Some(comment
) = comment
{
639 let comment
= comment
.trim().to_string();
640 if comment
.is_empty() {
643 data
.comment
= Some(comment
);
647 if let Some(enable
) = enable
{
648 data
.enable
= if enable { None }
else { Some(false) }
;
651 if let Some(expire
) = expire
{
652 data
.expire
= if expire
> 0 { Some(expire) }
else { None }
;
655 config
.set_data(&tokenid_string
, "token", &data
)?
;
657 user
::save_config(&config
)?
;
674 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
679 permission
: &Permission
::Or(&[
680 &Permission
::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY
, false),
681 &Permission
::UserParam("userid"),
685 /// Delete a user's API token
688 tokenname
: Tokenname
,
689 digest
: Option
<String
>,
690 ) -> Result
<(), Error
> {
692 let _lock
= open_backup_lockfile(user
::USER_CFG_LOCKFILE
, None
, true)?
;
694 let (mut config
, expected_digest
) = user
::config()?
;
696 if let Some(ref digest
) = digest
{
697 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
698 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
701 let tokenid
= Authid
::from((userid
.clone(), Some(tokenname
.clone())));
702 let tokenid_string
= tokenid
.to_string();
704 match config
.sections
.get(&tokenid_string
) {
705 Some(_
) => { config.sections.remove(&tokenid_string); }
,
706 None
=> bail
!("token '{}' of user '{}' does not exist.", tokenname
.as_str(), userid
),
709 token_shadow
::delete_secret(&tokenid
)?
;
711 user
::save_config(&config
)?
;
725 description
: "List user's API tokens (with config digest).",
727 items
: { type: user::ApiToken }
,
730 permission
: &Permission
::Or(&[
731 &Permission
::Privilege(&["access", "users"], PRIV_SYS_AUDIT
, false),
732 &Permission
::UserParam("userid"),
736 /// List user's API tokens
740 mut rpcenv
: &mut dyn RpcEnvironment
,
741 ) -> Result
<Vec
<user
::ApiToken
>, Error
> {
743 let (config
, digest
) = user
::config()?
;
745 let list
:Vec
<user
::ApiToken
> = config
.convert_to_typed_array("token")?
;
747 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
749 let filter_by_owner
= |token
: &user
::ApiToken
| {
750 if token
.tokenid
.is_token() {
751 token
.tokenid
.user() == &userid
757 Ok(list
.into_iter().filter(filter_by_owner
).collect())
760 const TOKEN_ITEM_ROUTER
: Router
= Router
::new()
761 .get(&API_METHOD_READ_TOKEN
)
762 .put(&API_METHOD_UPDATE_TOKEN
)
763 .post(&API_METHOD_GENERATE_TOKEN
)
764 .delete(&API_METHOD_DELETE_TOKEN
);
766 const TOKEN_ROUTER
: Router
= Router
::new()
767 .get(&API_METHOD_LIST_TOKENS
)
768 .match_all("tokenname", &TOKEN_ITEM_ROUTER
);
770 const USER_SUBDIRS
: SubdirMap
= &[
771 ("token", &TOKEN_ROUTER
),
774 const USER_ROUTER
: Router
= Router
::new()
775 .get(&API_METHOD_READ_USER
)
776 .put(&API_METHOD_UPDATE_USER
)
777 .delete(&API_METHOD_DELETE_USER
)
778 .subdirs(USER_SUBDIRS
);
780 pub const ROUTER
: Router
= Router
::new()
781 .get(&API_METHOD_LIST_USERS
)
782 .post(&API_METHOD_CREATE_USER
)
783 .match_all("userid", &USER_ROUTER
);