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