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