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