Signed by hannaSSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
crates/cli/src/user_cmd.rs +60 −0
| @@ -66,6 +66,14 @@ pub(crate) enum UserCommand { | |||
| 66 | 66 | /// The user to verify. | |
| 67 | 67 | name: String, | |
| 68 | 68 | }, | |
| 69 | + | /// Grant (or, with `--revoke`, remove) a user's site-administrator flag. | |
| 70 | + | Admin { | |
| 71 | + | /// The user to promote or demote. | |
| 72 | + | name: String, | |
| 73 | + | /// Revoke admin instead of granting it. | |
| 74 | + | #[arg(long)] | |
| 75 | + | revoke: bool, | |
| 76 | + | }, | |
| 69 | 77 | /// Delete a user. Refuses if the user still owns repositories unless | |
| 70 | 78 | /// `--purge` is given. | |
| 71 | 79 | Del { | |
| @@ -106,6 +114,7 @@ pub(crate) fn run( | |||
| 106 | 114 | UserCommand::Disable { name } => block_on(set_disabled(config_path, name, true)), | |
| 107 | 115 | UserCommand::Enable { name } => block_on(set_disabled(config_path, name, false)), | |
| 108 | 116 | UserCommand::Verify { name } => block_on(verify(config_path, name)), | |
| 117 | + | UserCommand::Admin { name, revoke } => block_on(set_admin(config_path, name, !*revoke)), | |
| 109 | 118 | UserCommand::Del { name, purge, yes } => block_on(del(config_path, name, *purge, *yes)), | |
| 110 | 119 | } | |
| 111 | 120 | } | |
| @@ -304,6 +313,31 @@ async fn verify(config_path: Option<&Path>, name: &str) -> anyhow::Result<ExitCo | |||
| 304 | 313 | Ok(ExitCode::SUCCESS) | |
| 305 | 314 | } | |
| 306 | 315 | ||
| 316 | + | /// Grant or revoke a user's site-administrator flag. | |
| 317 | + | async fn set_admin( | |
| 318 | + | config_path: Option<&Path>, | |
| 319 | + | name: &str, | |
| 320 | + | admin: bool, | |
| 321 | + | ) -> anyhow::Result<ExitCode> { | |
| 322 | + | let (_config, store) = open_store(config_path).await?; | |
| 323 | + | let user = require_user(&store, name).await?; | |
| 324 | + | if user.is_admin == admin { | |
| 325 | + | println!( | |
| 326 | + | "{} is already {}an administrator", | |
| 327 | + | user.username, | |
| 328 | + | if admin { "" } else { "not " } | |
| 329 | + | ); | |
| 330 | + | return Ok(ExitCode::SUCCESS); | |
| 331 | + | } | |
| 332 | + | store.set_admin(&user.id, admin).await?; | |
| 333 | + | println!( | |
| 334 | + | "{} admin for {}", | |
| 335 | + | if admin { "granted" } else { "revoked" }, | |
| 336 | + | user.username | |
| 337 | + | ); | |
| 338 | + | Ok(ExitCode::SUCCESS) | |
| 339 | + | } | |
| 340 | + | ||
| 307 | 341 | async fn del( | |
| 308 | 342 | config_path: Option<&Path>, | |
| 309 | 343 | name: &str, | |
| @@ -520,6 +554,32 @@ mod tests { | |||
| 520 | 554 | } | |
| 521 | 555 | ||
| 522 | 556 | #[test] | |
| 557 | + | fn admin_grants_then_revokes_the_flag() { | |
| 558 | + | let env = env(); | |
| 559 | + | block_on(add( | |
| 560 | + | env.path(), | |
| 561 | + | "erin", | |
| 562 | + | "erin@example.com", | |
| 563 | + | Some("pw".to_string()), | |
| 564 | + | false, | |
| 565 | + | None, | |
| 566 | + | )) | |
| 567 | + | .unwrap(); | |
| 568 | + | ||
| 569 | + | block_on(set_admin(env.path(), "erin", true)).unwrap(); | |
| 570 | + | let promoted = | |
| 571 | + | block_on(async { anyhow::Ok(require_user(&env.store().await, "erin").await?) }) | |
| 572 | + | .unwrap(); | |
| 573 | + | assert!(promoted.is_admin); | |
| 574 | + | ||
| 575 | + | block_on(set_admin(env.path(), "erin", false)).unwrap(); | |
| 576 | + | let demoted = | |
| 577 | + | block_on(async { anyhow::Ok(require_user(&env.store().await, "erin").await?) }) | |
| 578 | + | .unwrap(); | |
| 579 | + | assert!(!demoted.is_admin); | |
| 580 | + | } | |
| 581 | + | ||
| 582 | + | #[test] | |
| 523 | 583 | fn del_refuses_repo_owner_without_purge_then_purges() { | |
| 524 | 584 | let env = env(); | |
| 525 | 585 | block_on(add( | |