]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/access/user.rs
clippy: allow api functions with many arguments
[proxmox-backup.git] / src / api2 / access / user.rs
CommitLineData
bf78f708
DM
1//! User Management
2
08ac90f9 3use anyhow::{bail, format_err, Error};
6746bbb1 4use serde::{Serialize, Deserialize};
942078c4 5use serde_json::{json, Value};
6746bbb1 6use std::collections::HashMap;
579728c6 7
d4f020f4 8use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission};
942078c4 9use proxmox::api::router::SubdirMap;
579728c6 10use proxmox::api::schema::{Schema, StringSchema};
98c259b4 11use proxmox::tools::fs::open_file_locked;
579728c6
DM
12
13use crate::api2::types::*;
14use crate::config::user;
942078c4 15use crate::config::token_shadow;
4f66423f 16use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY};
be3bd0f9 17use crate::config::cached_user_info::CachedUserInfo;
579728c6
DM
18
19pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
20 .format(&PASSWORD_FORMAT)
21 .min_length(5)
22 .max_length(64)
23 .schema();
24
6746bbb1
FG
25#[api(
26 properties: {
27 userid: {
28 type: Userid,
29 },
30 comment: {
31 optional: true,
32 schema: SINGLE_LINE_COMMENT_SCHEMA,
33 },
34 enable: {
35 optional: true,
36 schema: user::ENABLE_USER_SCHEMA,
37 },
38 expire: {
39 optional: true,
40 schema: user::EXPIRE_USER_SCHEMA,
41 },
42 firstname: {
43 optional: true,
44 schema: user::FIRST_NAME_SCHEMA,
45 },
46 lastname: {
47 schema: user::LAST_NAME_SCHEMA,
48 optional: true,
49 },
50 email: {
51 schema: user::EMAIL_SCHEMA,
52 optional: true,
53 },
54 tokens: {
55 type: Array,
56 optional: true,
57 description: "List of user's API tokens.",
58 items: {
59 type: user::ApiToken
60 },
61 },
62 }
63)]
64#[derive(Serialize,Deserialize)]
65/// User properties with added list of ApiTokens
66pub struct UserWithTokens {
67 pub userid: Userid,
68 #[serde(skip_serializing_if="Option::is_none")]
69 pub comment: Option<String>,
70 #[serde(skip_serializing_if="Option::is_none")]
71 pub enable: Option<bool>,
72 #[serde(skip_serializing_if="Option::is_none")]
73 pub expire: Option<i64>,
74 #[serde(skip_serializing_if="Option::is_none")]
75 pub firstname: Option<String>,
76 #[serde(skip_serializing_if="Option::is_none")]
77 pub lastname: Option<String>,
78 #[serde(skip_serializing_if="Option::is_none")]
79 pub email: Option<String>,
b59c3082 80 #[serde(skip_serializing_if="Vec::is_empty", default)]
ea1853a1 81 pub tokens: Vec<user::ApiToken>,
6746bbb1
FG
82}
83
84impl UserWithTokens {
85 fn new(user: user::User) -> Self {
86 Self {
87 userid: user.userid,
88 comment: user.comment,
89 enable: user.enable,
90 expire: user.expire,
91 firstname: user.firstname,
92 lastname: user.lastname,
93 email: user.email,
ea1853a1 94 tokens: Vec::new(),
6746bbb1
FG
95 }
96 }
97}
98
579728c6
DM
99#[api(
100 input: {
6746bbb1
FG
101 properties: {
102 include_tokens: {
103 type: bool,
104 description: "Include user's API tokens in returned list.",
105 optional: true,
106 default: false,
107 },
108 },
579728c6
DM
109 },
110 returns: {
111 description: "List users (with config digest).",
112 type: Array,
906ef6c5 113 items: { type: UserWithTokens },
579728c6 114 },
d4f020f4 115 access: {
be3bd0f9 116 permission: &Permission::Anybody,
08ac90f9 117 description: "Returns all or just the logged-in user (/API token owner), depending on privileges.",
d4f020f4 118 },
579728c6 119)]
be3bd0f9 120/// List users
579728c6 121pub fn list_users(
6746bbb1 122 include_tokens: bool,
579728c6 123 _info: &ApiMethod,
522c0da0 124 mut rpcenv: &mut dyn RpcEnvironment,
6746bbb1 125) -> Result<Vec<UserWithTokens>, Error> {
579728c6
DM
126
127 let (config, digest) = user::config()?;
128
08ac90f9
FG
129 let auth_id: Authid = rpcenv
130 .get_auth_id()
131 .ok_or_else(|| format_err!("no authid available"))?
132 .parse()?;
133
134 let userid = auth_id.user();
e6dc35ac 135
be3bd0f9
FG
136 let user_info = CachedUserInfo::new()?;
137
e6dc35ac 138 let top_level_privs = user_info.lookup_privs(&auth_id, &["access", "users"]);
be3bd0f9
FG
139 let top_level_allowed = (top_level_privs & PRIV_SYS_AUDIT) != 0;
140
141 let filter_by_privs = |user: &user::User| {
08ac90f9 142 top_level_allowed || user.userid == *userid
be3bd0f9
FG
143 };
144
6746bbb1 145
be3bd0f9 146 let list:Vec<user::User> = config.convert_to_typed_array("user")?;
522c0da0
DC
147
148 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
579728c6 149
6746bbb1
FG
150 let iter = list.into_iter().filter(filter_by_privs);
151 let list = if include_tokens {
ea1853a1 152 let tokens: Vec<user::ApiToken> = config.convert_to_typed_array("token")?;
6746bbb1
FG
153 let mut user_to_tokens = tokens
154 .into_iter()
155 .fold(
156 HashMap::new(),
157 |mut map: HashMap<Userid, Vec<user::ApiToken>>, token: user::ApiToken| {
158 if token.tokenid.is_token() {
159 map
160 .entry(token.tokenid.user().clone())
161 .or_default()
162 .push(token);
163 }
164 map
165 });
166 iter
167 .map(|user: user::User| {
168 let mut user = UserWithTokens::new(user);
ea1853a1 169 user.tokens = user_to_tokens.remove(&user.userid).unwrap_or_default();
6746bbb1
FG
170 user
171 })
172 .collect()
173 } else {
22a9189e 174 iter.map(UserWithTokens::new)
6746bbb1
FG
175 .collect()
176 };
177
178 Ok(list)
579728c6
DM
179}
180
181#[api(
182 protected: true,
183 input: {
184 properties: {
185 userid: {
e7cb4dc5 186 type: Userid,
579728c6
DM
187 },
188 comment: {
189 schema: SINGLE_LINE_COMMENT_SCHEMA,
190 optional: true,
191 },
192 password: {
193 schema: PBS_PASSWORD_SCHEMA,
194 optional: true,
195 },
196 enable: {
197 schema: user::ENABLE_USER_SCHEMA,
198 optional: true,
199 },
200 expire: {
201 schema: user::EXPIRE_USER_SCHEMA,
202 optional: true,
203 },
204 firstname: {
205 schema: user::FIRST_NAME_SCHEMA,
206 optional: true,
207 },
208 lastname: {
209 schema: user::LAST_NAME_SCHEMA,
210 optional: true,
211 },
212 email: {
213 schema: user::EMAIL_SCHEMA,
214 optional: true,
215 },
216 },
217 },
d4f020f4 218 access: {
74c08a57 219 permission: &Permission::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY, false),
d4f020f4 220 },
579728c6
DM
221)]
222/// Create new user.
5aa10190
OB
223pub fn create_user(
224 password: Option<String>,
225 param: Value,
226 rpcenv: &mut dyn RpcEnvironment
227) -> Result<(), Error> {
579728c6 228
b56c111e 229 let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
579728c6 230
7d4e3629 231 let user: user::User = serde_json::from_value(param)?;
579728c6
DM
232
233 let (mut config, _digest) = user::config()?;
234
3984a5fd 235 if config.sections.get(user.userid.as_str()).is_some() {
7d4e3629 236 bail!("user '{}' already exists.", user.userid);
579728c6
DM
237 }
238
e7cb4dc5 239 config.set_data(user.userid.as_str(), "user", &user)?;
579728c6 240
5aa10190
OB
241 let realm = user.userid.realm();
242
243 // Fails if realm does not exist!
244 let authenticator = crate::auth::lookup_authenticator(realm)?;
245
579728c6
DM
246 user::save_config(&config)?;
247
7d817b03 248 if let Some(password) = password {
5aa10190
OB
249 let user_info = CachedUserInfo::new()?;
250 let current_auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
251 if realm == "pam" && !user_info.is_superuser(&current_auth_id) {
252 bail!("only superuser can edit pam credentials!");
253 }
e7cb4dc5 254 authenticator.store_password(user.userid.name(), &password)?;
7d817b03
DM
255 }
256
579728c6
DM
257 Ok(())
258}
259
260#[api(
261 input: {
262 properties: {
263 userid: {
e7cb4dc5 264 type: Userid,
579728c6 265 },
685e1334 266 },
579728c6 267 },
9b93c620 268 returns: { type: user::User },
d4f020f4 269 access: {
be3bd0f9
FG
270 permission: &Permission::Or(&[
271 &Permission::Privilege(&["access", "users"], PRIV_SYS_AUDIT, false),
272 &Permission::UserParam("userid"),
273 ]),
d4f020f4 274 },
579728c6
DM
275)]
276/// Read user configuration data.
e7cb4dc5 277pub fn read_user(userid: Userid, mut rpcenv: &mut dyn RpcEnvironment) -> Result<user::User, Error> {
579728c6 278 let (config, digest) = user::config()?;
e7cb4dc5 279 let user = config.lookup("user", userid.as_str())?;
522c0da0
DC
280 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
281 Ok(user)
579728c6
DM
282}
283
c0026563
DC
284#[api()]
285#[derive(Serialize, Deserialize)]
286#[serde(rename_all="kebab-case")]
287#[allow(non_camel_case_types)]
288pub enum DeletableProperty {
289 /// Delete the comment property.
290 comment,
291 /// Delete the firstname property.
292 firstname,
293 /// Delete the lastname property.
294 lastname,
295 /// Delete the email property.
296 email,
297}
298
579728c6
DM
299#[api(
300 protected: true,
301 input: {
302 properties: {
303 userid: {
e7cb4dc5 304 type: Userid,
579728c6
DM
305 },
306 comment: {
307 optional: true,
308 schema: SINGLE_LINE_COMMENT_SCHEMA,
309 },
310 password: {
311 schema: PBS_PASSWORD_SCHEMA,
312 optional: true,
313 },
314 enable: {
315 schema: user::ENABLE_USER_SCHEMA,
316 optional: true,
317 },
318 expire: {
319 schema: user::EXPIRE_USER_SCHEMA,
320 optional: true,
321 },
322 firstname: {
323 schema: user::FIRST_NAME_SCHEMA,
324 optional: true,
325 },
326 lastname: {
327 schema: user::LAST_NAME_SCHEMA,
328 optional: true,
329 },
330 email: {
331 schema: user::EMAIL_SCHEMA,
332 optional: true,
333 },
c0026563
DC
334 delete: {
335 description: "List of properties to delete.",
336 type: Array,
337 optional: true,
338 items: {
339 type: DeletableProperty,
340 }
341 },
579728c6
DM
342 digest: {
343 optional: true,
344 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
345 },
346 },
347 },
d4f020f4 348 access: {
be3bd0f9
FG
349 permission: &Permission::Or(&[
350 &Permission::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY, false),
351 &Permission::UserParam("userid"),
352 ]),
d4f020f4 353 },
579728c6
DM
354)]
355/// Update user configuration.
367c0ff7 356#[allow(clippy::too_many_arguments)]
579728c6 357pub fn update_user(
e7cb4dc5 358 userid: Userid,
579728c6
DM
359 comment: Option<String>,
360 enable: Option<bool>,
361 expire: Option<i64>,
362 password: Option<String>,
363 firstname: Option<String>,
364 lastname: Option<String>,
365 email: Option<String>,
c0026563 366 delete: Option<Vec<DeletableProperty>>,
579728c6 367 digest: Option<String>,
5aa10190 368 rpcenv: &mut dyn RpcEnvironment,
579728c6
DM
369) -> Result<(), Error> {
370
b56c111e 371 let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
579728c6
DM
372
373 let (mut config, expected_digest) = user::config()?;
374
375 if let Some(ref digest) = digest {
376 let digest = proxmox::tools::hex_to_digest(digest)?;
377 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
378 }
379
e7cb4dc5 380 let mut data: user::User = config.lookup("user", userid.as_str())?;
579728c6 381
c0026563
DC
382 if let Some(delete) = delete {
383 for delete_prop in delete {
384 match delete_prop {
385 DeletableProperty::comment => data.comment = None,
386 DeletableProperty::firstname => data.firstname = None,
387 DeletableProperty::lastname => data.lastname = None,
388 DeletableProperty::email => data.email = None,
389 }
390 }
391 }
392
579728c6
DM
393 if let Some(comment) = comment {
394 let comment = comment.trim().to_string();
395 if comment.is_empty() {
396 data.comment = None;
397 } else {
398 data.comment = Some(comment);
399 }
400 }
401
402 if let Some(enable) = enable {
403 data.enable = if enable { None } else { Some(false) };
404 }
405
406 if let Some(expire) = expire {
407 data.expire = if expire > 0 { Some(expire) } else { None };
408 }
409
410 if let Some(password) = password {
5aa10190
OB
411 let user_info = CachedUserInfo::new()?;
412 let current_auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
413 let self_service = current_auth_id.user() == &userid;
414 let target_realm = userid.realm();
415 if !self_service && target_realm == "pam" && !user_info.is_superuser(&current_auth_id) {
416 bail!("only superuser can edit pam credentials!");
417 }
e7cb4dc5
WB
418 let authenticator = crate::auth::lookup_authenticator(userid.realm())?;
419 authenticator.store_password(userid.name(), &password)?;
579728c6
DM
420 }
421
422 if let Some(firstname) = firstname {
423 data.firstname = if firstname.is_empty() { None } else { Some(firstname) };
424 }
425
426 if let Some(lastname) = lastname {
427 data.lastname = if lastname.is_empty() { None } else { Some(lastname) };
428 }
429 if let Some(email) = email {
430 data.email = if email.is_empty() { None } else { Some(email) };
431 }
432
e7cb4dc5 433 config.set_data(userid.as_str(), "user", &data)?;
579728c6
DM
434
435 user::save_config(&config)?;
436
437 Ok(())
438}
439
440#[api(
441 protected: true,
442 input: {
443 properties: {
444 userid: {
e7cb4dc5 445 type: Userid,
579728c6
DM
446 },
447 digest: {
448 optional: true,
449 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
450 },
451 },
452 },
d4f020f4 453 access: {
be3bd0f9
FG
454 permission: &Permission::Or(&[
455 &Permission::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY, false),
456 &Permission::UserParam("userid"),
457 ]),
d4f020f4 458 },
579728c6
DM
459)]
460/// Remove a user from the configuration file.
e7cb4dc5 461pub fn delete_user(userid: Userid, digest: Option<String>) -> Result<(), Error> {
579728c6 462
f22dfb5e 463 let _tfa_lock = crate::config::tfa::write_lock()?;
b56c111e 464 let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
579728c6
DM
465
466 let (mut config, expected_digest) = user::config()?;
467
468 if let Some(ref digest) = digest {
469 let digest = proxmox::tools::hex_to_digest(digest)?;
470 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
471 }
472
e7cb4dc5
WB
473 match config.sections.get(userid.as_str()) {
474 Some(_) => { config.sections.remove(userid.as_str()); },
579728c6
DM
475 None => bail!("user '{}' does not exist.", userid),
476 }
477
478 user::save_config(&config)?;
479
f22dfb5e
WB
480 match crate::config::tfa::read().and_then(|mut cfg| {
481 let _: bool = cfg.remove_user(&userid);
482 crate::config::tfa::write(&cfg)
483 }) {
484 Ok(()) => (),
485 Err(err) => {
486 eprintln!(
487 "error updating TFA config after deleting user {:?}: {}",
488 userid, err
489 );
490 }
491 }
492
579728c6
DM
493 Ok(())
494}
495
942078c4
FG
496#[api(
497 input: {
498 properties: {
499 userid: {
500 type: Userid,
501 },
502 tokenname: {
503 type: Tokenname,
504 },
505 },
506 },
9b93c620 507 returns: { type: user::ApiToken },
942078c4
FG
508 access: {
509 permission: &Permission::Or(&[
510 &Permission::Privilege(&["access", "users"], PRIV_SYS_AUDIT, false),
511 &Permission::UserParam("userid"),
512 ]),
513 },
514)]
515/// Read user's API token metadata
516pub fn read_token(
517 userid: Userid,
518 tokenname: Tokenname,
519 _info: &ApiMethod,
520 mut rpcenv: &mut dyn RpcEnvironment,
521) -> Result<user::ApiToken, Error> {
522
523 let (config, digest) = user::config()?;
524
525 let tokenid = Authid::from((userid, Some(tokenname)));
526
527 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
528 config.lookup("token", &tokenid.to_string())
529}
530
531#[api(
532 protected: true,
533 input: {
534 properties: {
535 userid: {
536 type: Userid,
537 },
538 tokenname: {
539 type: Tokenname,
540 },
541 comment: {
542 optional: true,
543 schema: SINGLE_LINE_COMMENT_SCHEMA,
544 },
545 enable: {
546 schema: user::ENABLE_USER_SCHEMA,
547 optional: true,
548 },
549 expire: {
550 schema: user::EXPIRE_USER_SCHEMA,
551 optional: true,
552 },
553 digest: {
554 optional: true,
555 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
556 },
557 },
558 },
559 access: {
560 permission: &Permission::Or(&[
561 &Permission::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY, false),
562 &Permission::UserParam("userid"),
563 ]),
564 },
565 returns: {
566 description: "API token identifier + generated secret.",
567 properties: {
568 value: {
569 type: String,
570 description: "The API token secret",
571 },
572 tokenid: {
573 type: String,
574 description: "The API token identifier",
575 },
576 },
577 },
578)]
579/// Generate a new API token with given metadata
580pub fn generate_token(
581 userid: Userid,
582 tokenname: Tokenname,
583 comment: Option<String>,
584 enable: Option<bool>,
585 expire: Option<i64>,
586 digest: Option<String>,
587) -> Result<Value, Error> {
588
589 let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
590
591 let (mut config, expected_digest) = user::config()?;
592
593 if let Some(ref digest) = digest {
594 let digest = proxmox::tools::hex_to_digest(digest)?;
595 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
596 }
597
598 let tokenid = Authid::from((userid.clone(), Some(tokenname.clone())));
599 let tokenid_string = tokenid.to_string();
600
3984a5fd 601 if config.sections.get(&tokenid_string).is_some() {
942078c4
FG
602 bail!("token '{}' for user '{}' already exists.", tokenname.as_str(), userid);
603 }
604
605 let secret = format!("{:x}", proxmox::tools::uuid::Uuid::generate());
606 token_shadow::set_secret(&tokenid, &secret)?;
607
608 let token = user::ApiToken {
44288184 609 tokenid,
942078c4
FG
610 comment,
611 enable,
612 expire,
613 };
614
615 config.set_data(&tokenid_string, "token", &token)?;
616
617 user::save_config(&config)?;
618
619 Ok(json!({
620 "tokenid": tokenid_string,
621 "value": secret
622 }))
623}
624
625#[api(
626 protected: true,
627 input: {
628 properties: {
629 userid: {
630 type: Userid,
631 },
632 tokenname: {
633 type: Tokenname,
634 },
635 comment: {
636 optional: true,
637 schema: SINGLE_LINE_COMMENT_SCHEMA,
638 },
639 enable: {
640 schema: user::ENABLE_USER_SCHEMA,
641 optional: true,
642 },
643 expire: {
644 schema: user::EXPIRE_USER_SCHEMA,
645 optional: true,
646 },
647 digest: {
648 optional: true,
649 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
650 },
651 },
652 },
653 access: {
654 permission: &Permission::Or(&[
655 &Permission::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY, false),
656 &Permission::UserParam("userid"),
657 ]),
658 },
659)]
660/// Update user's API token metadata
661pub fn update_token(
662 userid: Userid,
663 tokenname: Tokenname,
664 comment: Option<String>,
665 enable: Option<bool>,
666 expire: Option<i64>,
667 digest: Option<String>,
668) -> Result<(), Error> {
669
670 let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
671
672 let (mut config, expected_digest) = user::config()?;
673
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)?;
677 }
678
679 let tokenid = Authid::from((userid, Some(tokenname)));
680 let tokenid_string = tokenid.to_string();
681
682 let mut data: user::ApiToken = config.lookup("token", &tokenid_string)?;
683
684 if let Some(comment) = comment {
685 let comment = comment.trim().to_string();
686 if comment.is_empty() {
687 data.comment = None;
688 } else {
689 data.comment = Some(comment);
690 }
691 }
692
693 if let Some(enable) = enable {
694 data.enable = if enable { None } else { Some(false) };
695 }
696
697 if let Some(expire) = expire {
698 data.expire = if expire > 0 { Some(expire) } else { None };
699 }
700
701 config.set_data(&tokenid_string, "token", &data)?;
702
703 user::save_config(&config)?;
704
705 Ok(())
706}
707
708#[api(
709 protected: true,
710 input: {
711 properties: {
712 userid: {
713 type: Userid,
714 },
715 tokenname: {
716 type: Tokenname,
717 },
718 digest: {
719 optional: true,
720 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
721 },
722 },
723 },
724 access: {
725 permission: &Permission::Or(&[
726 &Permission::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY, false),
727 &Permission::UserParam("userid"),
728 ]),
729 },
730)]
731/// Delete a user's API token
732pub fn delete_token(
733 userid: Userid,
734 tokenname: Tokenname,
735 digest: Option<String>,
736) -> Result<(), Error> {
737
738 let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
739
740 let (mut config, expected_digest) = user::config()?;
741
742 if let Some(ref digest) = digest {
743 let digest = proxmox::tools::hex_to_digest(digest)?;
744 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
745 }
746
747 let tokenid = Authid::from((userid.clone(), Some(tokenname.clone())));
748 let tokenid_string = tokenid.to_string();
749
750 match config.sections.get(&tokenid_string) {
751 Some(_) => { config.sections.remove(&tokenid_string); },
752 None => bail!("token '{}' of user '{}' does not exist.", tokenname.as_str(), userid),
753 }
754
755 token_shadow::delete_secret(&tokenid)?;
756
757 user::save_config(&config)?;
758
759 Ok(())
760}
761
762#[api(
763 input: {
764 properties: {
765 userid: {
766 type: Userid,
767 },
768 },
769 },
770 returns: {
771 description: "List user's API tokens (with config digest).",
772 type: Array,
773 items: { type: user::ApiToken },
774 },
775 access: {
776 permission: &Permission::Or(&[
777 &Permission::Privilege(&["access", "users"], PRIV_SYS_AUDIT, false),
778 &Permission::UserParam("userid"),
779 ]),
780 },
781)]
782/// List user's API tokens
783pub fn list_tokens(
784 userid: Userid,
785 _info: &ApiMethod,
786 mut rpcenv: &mut dyn RpcEnvironment,
787) -> Result<Vec<user::ApiToken>, Error> {
788
789 let (config, digest) = user::config()?;
790
791 let list:Vec<user::ApiToken> = config.convert_to_typed_array("token")?;
792
793 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
794
795 let filter_by_owner = |token: &user::ApiToken| {
796 if token.tokenid.is_token() {
797 token.tokenid.user() == &userid
798 } else {
799 false
800 }
801 };
802
803 Ok(list.into_iter().filter(filter_by_owner).collect())
804}
805
806const TOKEN_ITEM_ROUTER: Router = Router::new()
807 .get(&API_METHOD_READ_TOKEN)
808 .put(&API_METHOD_UPDATE_TOKEN)
809 .post(&API_METHOD_GENERATE_TOKEN)
810 .delete(&API_METHOD_DELETE_TOKEN);
811
812const TOKEN_ROUTER: Router = Router::new()
813 .get(&API_METHOD_LIST_TOKENS)
814 .match_all("tokenname", &TOKEN_ITEM_ROUTER);
815
816const USER_SUBDIRS: SubdirMap = &[
817 ("token", &TOKEN_ROUTER),
818];
819
820const USER_ROUTER: Router = Router::new()
579728c6
DM
821 .get(&API_METHOD_READ_USER)
822 .put(&API_METHOD_UPDATE_USER)
942078c4
FG
823 .delete(&API_METHOD_DELETE_USER)
824 .subdirs(USER_SUBDIRS);
579728c6
DM
825
826pub const ROUTER: Router = Router::new()
827 .get(&API_METHOD_LIST_USERS)
828 .post(&API_METHOD_CREATE_USER)
942078c4 829 .match_all("userid", &USER_ROUTER);