]> git.proxmox.com Git - proxmox-backup.git/blame_incremental - src/api2/access/user.rs
update to proxmox-sys 0.2 crate
[proxmox-backup.git] / src / api2 / access / user.rs
... / ...
CommitLineData
1//! User Management
2
3use anyhow::{bail, format_err, Error};
4use serde::{Serialize, Deserialize};
5use serde_json::{json, Value};
6use std::collections::HashMap;
7use hex::FromHex;
8
9use proxmox_router::{ApiMethod, Router, RpcEnvironment, SubdirMap, Permission};
10use proxmox_schema::api;
11
12use 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};
18use pbs_config::token_shadow;
19
20use pbs_config::CachedUserInfo;
21
22fn 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
57pub 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.
136pub 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(&section_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(&current_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.
188pub 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)]
199pub 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)]
248pub 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(&current_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.
347pub 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
414pub 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
478pub 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
559pub 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
630pub 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
669pub 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
697pub 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
726const 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
732const TOKEN_ROUTER: Router = Router::new()
733 .get(&API_METHOD_LIST_TOKENS)
734 .match_all("token-name", &TOKEN_ITEM_ROUTER);
735
736const USER_SUBDIRS: SubdirMap = &[
737 ("token", &TOKEN_ROUTER),
738];
739
740const 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
746pub const ROUTER: Router = Router::new()
747 .get(&API_METHOD_LIST_USERS)
748 .post(&API_METHOD_CREATE_USER)
749 .match_all("userid", &USER_ROUTER);