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